/* jshint esversion: 11 */
/*jshint -W069 */

// 全局变量
var INFO = {
    name: "ChatWsClient",
    intro: "用于连接到ChatWss的Websocket客户端",
    version: [4, 5, 1],
    other: {
        auth: "Wn1027",
    }
};

/* 
同步文件
开放协调扩展接口
登录需要记录uid等client信息。

×统一传送数据格式规范
×优化playerInfo本地存储（需要各服务器的playerInfo来记录uuid以及记录玩家意外退出状态）

要想自动修复异常退出状态，上次登录状态以主服还是副服为准？副服修复以副服为准，登录判断以主服为准
上次登录状态能否另外存储到键值里面，而不是写在玩家状态里面？ 不行。
是否保留附属服务器的玩家数据库？ 保留，用于意外退出判断。
如何优雅的读写本地存储的玩家状态信息？哪些可以省去，哪些不可以？考虑用json存储？
*/

var WebSocket_module; // Nodejs ws模块
let PAPI;
let GMLIB_MC;
let GMLIB_SC;

var serverconfig =  File.readFrom('./server.properties');
var Levelname = /level-name=(.*)/.exec(serverconfig)[1];  //获取存档名
var BDSPort = Number(/server-port=(.*)/.exec(serverconfig)[1]); //获取端口

global.websocket = {ws:undefined};

var Attempts = 0; // 已经尝试重连的次数
var Executor = Levelname; //由本插件自动决定BDS命令执行者
var Subscribe = {PlayerMessage : false}; //原版MC支持的事件订阅
var onJoinSync = {}; // 登录同步状态（该玩家必须登录时进行第一次同步，否则无法自动同步
var OnlineBDSInfo = {}; // 云端在线的服务器{BDSUid:{ServerName:"", BDSType: "", BDSIp: "", BDSPort: 19132}}
let mainBDS = {online: false, uid: -1, ServerName: "主服务器", BDSUid: "", BDSIp: "", BDSPort: 19132};
let WSSstate = {
    wscReady: false,
    bdsReady: false,
};
let ServerConfig = {};
var OnlinePlayers = {}; // 云端在线的玩家
var OnLLmoney = new Set();// 正在同步LLMoney的玩家[xuid, xuid, xuid]
const wsSendCache = {
    count: 1,
    funcArr: {}
};
var IsLLSEPlugin = true; //本客户端是否作为llse插件，否则是nodejs的lib

//-------------------------------------------------------------
//生成配置文件与目录
var defaultConfig = { 
    "CONFIG": "此为Websocket客户端配置文件",
    // Websocket网络
    "ConnectUrl": "ws://127.0.0.1:9001",           // 要连接的Websocket地址（优先局域网地址）
    "ConnectKey": "FlySteve",                      // 连接密钥，暂时没用
    "BDSUid": system.randomGuid(),                 // BDS唯一识别名（每个服务器一个, 不能重复, 不可擅自修改"）
    "BDSIp": getPublicIP(),                        // 此BDS的公网IP地址
    "BDSPort": BDSPort,                            // 此BDS的公网端口
    
    // 聊天配置
    "ServerName": Levelname,                       // 服务器名称 （默认为存档名） & 提示信息前缀
    "ChatWSDisplay": false,                        // WS控制台聊天显示
    "ExecutorLevel": 1,                            // 云端./命令执行权限: 1为仅op可执行./命令, 0为任何在线玩家都可执行

    // 玩家数据同步配置
    "SyncConfig":{
        "Enabled": true,                               // 同步功能总开关
        "PlayerDbDir": "./plugins/chatwss/PlayerData", // 玩家数据库路径
        "PlayerDbFormat": "leveldb",                   // 玩家数据储存格式（'leveldb' or 'json'）
        "BackupEnabled": true,                         // 是否启用玩家状态数据备份
        "BackupTime": ["0 0 22 * * *"],                // 玩家状态数据备份时间(Cron表达式) (默认是22:00备份一次) (务必与存档备份时间同步)
        "BackupDir": "./backup/chatwss",               // 玩家状态数据备份路径（时间上跟随存档备份）
        "BackupRemainDays": 15,                        // 玩家状态数据备份留存天数：数据量不大，可以适当多存点
        "ServerType": 'sub',                           // BDS类型, 用于玩家数据同步, 副服: 'sub' 主服: 'main', 玩家数据以主服数据为准。
        "SafeLeftMode" : true,                         // 安全退出模式：上次玩家从服务器非正常退出(onLeft(pl)没有监听到), 则该玩家必须先登录上次服务器。
        "AllowCreateNewPlayer": false,                  // 是否允许在存档新建玩家。（新玩家登录副服不用传送主服）
        "AutoSyncCycle": 30000,                        // 自动同步周期(ms)
        "SyncOption": {                                // 玩家数据同步选项（这里是本服作为同步数据接收方的同步项开关）
            "syncInventory": true,                  // 跨服背包（非实时同步）
            "syncLLMoney": true,                    // 跨服LLMoney（实时同步）
            "syncScoreboard": [],                   // 跨服计分板（非实时同步）填入想要同步的计分项
            "syncTags": true,                       // 跨服Tag
            "syncEnderChest": true,                 // 跨服末影箱
            "syncLevel": true,                      // 跨服经验等级
            "syncEffects": false,                   // 跨服药水效果
            "syncCmd": ["allowlist", "whitelist"]   // 同步执行的命令(主服执行，则其他服也同步执行);
        }
    }
};

var config = new JsonConfigFile("./plugins/chatwss/ClientConfig.json", JSON.stringify(defaultConfig)); //客户端配置文件路径
//配置升级
function clientConfigUpdate(){
    if (config.get("SyncConfig").SyncOption.syncCmd == undefined){
        let SyncConfig = config.get("SyncConfig");
        SyncConfig.SyncOption.syncCmd = ["allowlist", "whitelist"];
        config.set("SyncConfig", SyncConfig);
        logger.warn(`【配置文件已升级】 ClientConfig.json/SyncConfig/SyncOption/ 增加了同步命令列表: "syncCmd" | 默认白名单同步, 主服执行，则其他服也执行`);  
    }

    if ([true, false].includes(config.get("SyncConfig").SyncOption.syncScoreboard)){
        let SyncConfig = config.get("SyncConfig");
        SyncConfig.SyncOption.syncScoreboard = [];
        config.set("SyncConfig", SyncConfig);
        logger.warn(`【配置文件已升级】 ClientConfig.json/SyncConfig/SyncOption/ 修改了同步计分板配置: "syncScoreboard" 为 [] | 请在列表中填入想要同步的计分项`);  
    }

    if (config.get("SyncConfig").PlayerDbFormat == undefined){
        let SyncConfig = config.get("SyncConfig");
        SyncConfig.PlayerDbFormat = "leveldb";
        config.set("SyncConfig", SyncConfig);
        logger.warn(`【配置文件已升级】 ClientConfig.json/SyncConfig 增加了玩家数据储存形式: "PlayerDbFormat" 为 "levedb" | 可选择改为 "json" `);  
    }

    if (config.get("BDSUid") == null){
        config.set("BDSUid", system.randomGuid());
        logger.warn(`【配置文件已升级】 ClientConfig.json 增加了BDS唯一识别名: "BDSUid" | 如你这是新数据库, 若有玩家进服后, 请勿随意修改此项`);   
        logger.warn(`【4.2.0升级】 本次升级不兼容旧版, 如你还未删除旧数据库, 请根据意外退出检查结果(旧数据库不会执行自动修复), 酌情备份存档和数据库后删除旧数据库`);
        logger.warn(`【4.2.0升级】 备份命令:sync backup | 数据库路径: "./plugins/chatwss/PlayerData/" | 数据库备份路径: "${BackupDir}"`);
        logger.warn(`【4.2.0升级】 如有玩家处于意外退出状态(崩服或有玩家情况下直接关服), 删除数据库会使该玩家失去30秒同步(自动同步周期)`);
        logger.warn(`【4.2.0升级】 删除数据库可能使该玩家多出一些物品或丢失一些物品, 但由于意外退出概率较低, 预计损失很小`);
    }

    if (config.get("SyncConfig").BackupEnabled == null){
        let SyncConfig = config.get("SyncConfig");
        SyncConfig.BackupEnabled = true;
        config.set("SyncConfig", SyncConfig);
    }
}
clientConfigUpdate();

let pldb;
if (config.get("SyncConfig")["PlayerDbFormat"] == "leveldb"){
    pldb = new KVDatabase(config.get("SyncConfig")["PlayerDbDir"]); // 玩家数据库路径;
}else{
    pldb = new JsonConfigFile(config.get("SyncConfig")["PlayerDbDir"]+"/pldb.json","{}"); // 玩家数据库路径;
}
var BackupDir = config.get("SyncConfig")["BackupDir"];  //玩家数据库备份路径

if(pldb.get("GUID") == null) {
    pldb.set("GUID", system.randomGuid());
}

//-------------------------------------------------------------
//BDS WS客户端配置变量（作为llse插件的其他服务器连接主服务器时使用）
    ConnectUrl = config.get("ConnectUrl"); //主服务器Websocket地址
    ConnectKey = config.get("ConnectKey"); // 连接密钥，暂时没用
    BDSUid = config.get("BDSUid");         // 此BDS的唯一识别名(不要随意修改)
    BDSIp = config.get("BDSIp");         // 此BDS的IP地址
    BDSPort = config.get("BDSPort");     // 此BDS的端口

    BackupTime = config.get("SyncConfig")["BackupTime"]; // cron 定时备份时间
    BackupRemainDays = config.get("SyncConfig")["BackupRemainDays"]; // 玩家数据备份留存天数

    ServerType = config.get("SyncConfig")["ServerType"]; //同步服务器类型
    AutoSyncCycle = config.get("SyncConfig")["AutoSyncCycle"]; // 自动同步周期

function configReload(){
    config.reload();
    ServerName = config.get("ServerName"); // 云端用户名&提示信息前缀可自定义
    ChatWSDisplay = config.get("ChatWSDisplay"); // WS控制台聊天显示
    ExecutorLevel = config.get("ExecutorLevel"); // 云端./命令执行权限 1 为仅op可执行./命令， 0为任何在线玩家

    SyncEnabled = config.get("SyncConfig")["Enabled"]; // 同步功能总开关
    SafeLeftMode = config.get("SyncConfig")["SafeLeftMode"]; // 安全退出模式
    AllowCreateNewPlayer = config.get("SyncConfig")["AllowCreateNewPlayer"] ?? true;
    SO = config.get("SyncConfig")["SyncOption"]; // 玩家数据同步选项
    // global.YoyoChatGroup = 793361363; //yoyo机器人聊天群号 云端消息也会转发到该群
}
configReload(); // 仅部分配置支持重载
//-------------------------------------------------------------
function getSOArray(SO){
    let array = [];
    if (SO["syncInventory"] == true){
        array.push("Inventory");
        array.push("Armor");
        array.push("Offhand");
        array.push("Attributes");
    }
    if (SO["syncTags"] == true){
        array.push("Tags");
    }
    if (SO["syncEnderChest"] == true){
        array.push("EnderChestInventory");
    }
    if (SO["syncLevel"] == true){
        array.push("PlayerLevel");
        array.push("PlayerLevelProgress");
    }
    if (SO["syncEffects"] == true){
        array.push("ActiveEffects");
    }
    return array;
}

logger.setConsole(true, 4); // 日志等级，4为默认，5为debug模式


//-------------------------------------------------------------
//-------------------------------------------------------------
// WS连接服务

// llse客户端功能：连接远程Websocket
function llseWsClient(){
    //Websocket
    global.CronJob = require('cron').CronJob;
    WebSocket_module = require("ws");
    let wsc = websocket.ws;
    ll.registerPlugin(INFO.name, INFO.intro, INFO.version, INFO.other);
    
    ScheduleBackup(); // 开启定时备份功能

    mc.listen("onServerStarted", ()=>{
        try{
            PAPI = require("../GMLIB-LegacyRemoteCallApi/lib/BEPlaceholderAPI-JS").PAPI;
            //require('../../lib/BEPlaceholderAPI-JS').PAPI;
            PAPI.registerServerPlaceholder(getWSSPlayersListNum, 'chatwss', 'wss_onlinePlayersNum');
            PAPI.registerServerPlaceholder(getWSSPlayersList, 'chatwss', `wss_onlinePlayers`);
        }catch(e){
            logger.warn('未导出PAPI变量 在线玩家 wss_onlinePlayers 在线人数 wss_onlinePlayersNum ');
        }

        try{
            GMLIB_MC = require("../GMLIB-LegacyRemoteCallApi/lib/GMLIB_API-JS").Minecraft;
            GMLIB_SC = require("../GMLIB-LegacyRemoteCallApi/lib/GMLIB_API-JS").Scoreboard;
        }catch(e){
            logger.error(e);
            logger.error('需要前置插件: GMLIB-LegacyRemoteCallApi');
        }

        cmdRegister();
        //三秒后开始连接
        setTimeout(()=>{
            connect();
            AutoSyncPlayerData(); //开启玩家数据自动同步
            setTimeout(()=>{
                HeartInterval(); // 开启心跳检测
            },3000);
        },3000);
    });
}

// 连接Websocket
function connect(){
    if (websocket.ws == undefined){
        websocket.ws = new WebSocket_module(`${ConnectUrl}?key=${ConnectKey}`);
        websocket.ws.on("close", onLostConnection);
        websocket.ws.on("error",onError);
        websocket.ws.on('message', (_data)=>onTextReceived(_data));
        //websocket.ws.on("onBinaryReceived", onBinaryReceived);

        websocket.ws.on('open', function open() {
            WSSstate.wscReady = true;
            Attempts = 0;
            logger.info(`WS协调客户端(ver.${INFO.version}) 已连接到 ${ConnectUrl}`);
            logger.info(`游戏内输入 ./help 查看帮助`);
            if (ServerType == "sub" && SyncEnabled  && SO.syncLLMoney){
                logger.info(`已开启LLMoney实时同步, 如果本附属服务器与WS协调服务端断开时间较长, 为保证数据同步, 请务必进行一次手动同步: 从主服务器复制粘贴LLmoney.db`);
            }
            // 玩家重连见 onTextReceived(data) refOnlineBDS基础服务

        });

        // ws.on('message', function message(data) {
        //   console.log('received: %s', data);
        // });
    }else{
        websocket.ws.close();
        //logger.warn(`该连接已占用, 解除占用并重新连接 ${ConnectUrl}!`);
        websocket.ws = undefined;
        connect();
    }  
}

// 监听连接丢失
function onLostConnection(code){
    WSSstate.wscReady = false;
    WSSstate.bdsReady = false;
    if (Attempts == 0){
        Attempts = 1;
        mc.broadcast(`§cWS协调服务已断开`);
        if (SyncEnabled){
            mc.broadcast(`§c为了防止失去数据同步, 玩家退出后无法重连。`);
        }
        if (SyncEnabled && SO.syncLLMoney){
            for (let pl of mc.getOnlinePlayers()){
                pl.disconnect(`§cWS协调服务已断开\n由于启用了LLMoney实时同步, 为防止失去同步, 本服禁止登入\n请联系服主启动主服务器`);
                logger.warn(`<${pl.realName}> 主服务器已断开, 为防止LLMoney失去同步, 本服禁止登入`);
            }
        }
    }
    logger.error(`连接已丢失 | 第 ${Attempts} 次 | 错误码: ${code} | ${ConnectUrl}`);    
}

// 心跳检测 Websocket连接 (15秒) (独立客户端插件，需真正的WS连接)
function HeartInterval(){
    let taskID = setInterval(() => {
        let wsc = websocket.ws;

        if (wsc == undefined){
            WSSstate.wscReady = false;
            WSSstate.bdsReady = false;
            reConnect(wsc);
            return;
        }else if(wsc.readyState == 3){
            wsc.close();
            wsc = undefined;
            WSSstate.wscReady = false;
            WSSstate.bdsReady = false;
            reConnect(wsc);
        }

        function reConnect(wsc){
            //logger.warn(`尝试重连协调服务端: 第 ${Attempts} 次 | 是否占用: ${wsc == undefined}`);
            if (connect() == true){
                Attempts = 0;
                return;
            }else{
                Attempts++;
            }
            
            // if (Attempts > 60){ //Math.floor(AutoSyncCycle/5000)-3
            //     clearInterval(taskID);
            //     logger.error(`重连到协调服务端失败, 15秒后关闭服务器`);

            //     mc.broadcast(`§a§l///////// §e提 示 §a/////////`);
            //     mc.broadcast(` §c失去同步 | 重连协调服务端失败`);
            //     mc.broadcast(` §c15秒后关闭服务器, 请联系服主`);
            //     mc.broadcast(`§a§l////////////////////////。`);

            //     setTimeout(()=>{
            //         mc.runcmdEx("stop"); //与云端同步服务器重连超时，自动关闭服务器 // 断连期间如果有其他玩家登入附属服务器，则会刷物或丢失物品
            //     },15000);
            // }
        }
        
    },15000);
}

// 监听错误
function onError(message){
    logger.debug(message);
}

// 监听二进制
function onBinaryReceived(message){
    logger.debug(`二进制消息: ${message}`);
}

// 【主要】 监听ws服务端消息（处理runcmd、subscrib、subscrib，发送comandReaquest）
function onTextReceived(data){
    // 监听收到的指令， 并处理返回执行结果：comandReaquest
    let wsc = websocket.ws;
    let json = {
        body: {
            victim: undefined,
            userType: 'server',
            position: undefined,
            statusCode: undefined,
            statusMessage: undefined
        },
        header: {
            messagePurpose: 'commandResponse',
            requestId: undefined,
            version: 16908288
        }
    };

    let Res = JSON.parse(data); // 解析收到的json

    //logger.debug("[Receive]: ", Res);
    let RequestId = {tag: null, uid:null}; //ws服务端命令一定有RequestId
    let purpose = Res.header.messagePurpose; // 服务端json目的

    let commandLine = Res.body.commandLine;  // 服务端命令
    RequestId =  {tag: Res.header.requestId.split("@")[0], uid:Res.header.requestId.split("@")[1]}; //命令tag信息，用于判断命令作用
    
    json.header.requestId = Res.header.requestId; // 原样返回requestId

    // 处理 收到的云端聊天（拦截tellraw命令，改为llseApi发送聊天消息）
    if (RequestId.tag == "chat"){
        //tellraw @a {"rawtext":[{"text":",发发发"}]}
        let msg = /^tellraw\s@a\s\{"rawtext":\[\{"text":"(.*)"\}\]\}$/.exec(commandLine)[1];
        //mc.broadcast(msg, 1); // 这样会有<server>的前缀
        mc.broadcast(msg);
        if (ChatWSDisplay == true){ //控制台聊天显示
            logger.info(msg);
        }
        // if (yoyorobot != undefined){
            // logger.info("send: "+msg)
            // yoyorobot('sendGroupMsg', [YoyoChatGroup, msg]); //转发云端消息到群聊
        // }
        
        return;
    }    

    // 注册的服务
    if (purpose == "BDServer"){
        BDServer_serveice(Res.body.data);
    }
    if (purpose == "Sync"){
        sync_serveice(Res.body.data);
    }
    

    // 处理BDS玩家同步数据
    // if (purpose == "Sync"){
    //     if (SyncEnabled == false){return;}
    //     let rcv_data = Res.body.data;
    //     logger.debug(`[Sync][通用消息] 收到消息 tag: ${rcv_data.tag}`);
    //     SyncHandle(rcv_data);
    //     return;
    // }


    // 处理 runcmd 命令
    if (purpose == "commandRequest" && commandLine != undefined){
        if (commandLine.indexOf("@s") != -1){
            //指定命令执行者
            commandLine = commandLine.replace(/@s/, `"${Executor.realName}"`);
        }
        let cmdRes = mc.runcmdEx(commandLine); //执行mc命令
        let statusCode; 
        if (cmdRes.success == true){
            statusCode = 0;
        }else{
            statusCode = -1;
        }

        json.body.statusMessage = cmdRes.output;
        json.body.statusCode = statusCode;

        // 特别回应 initName 命令请求
        if (RequestId.tag == "initName"){
            json.body.victim = [ServerName]; // 云端用户名
            json.body.userType = 'server'; // 初始化用户类型为server
            json.body.BDSType = ServerType; // 同步服务器类型 附属服务器(sub) or 主服务器(main)
            json.body.BDSUid = BDSUid; // BDS唯一识别名
            json.body.BDSIp = BDSIp; // 本机IP
            json.body.BDSPort = BDSPort; // 本机端口
        }

        // 特别回应 initPos 命令请求
        if (RequestId.tag == "initPos"){
            json.body.position = { x: 0, y: -64, z: 0 };
        }else if(RequestId.tag == "locatePos"){
            json.body.position = { x: Executor.blockPos.x, y:  Executor.blockPos.y, z:  Executor.blockPos.z};
        }

        // 发送回应
        //logger.debug("[Send]: ", JSON.stringify(json));
        if (wsc.isLocal == true){
            wsc.ws.emit("message", JSON.stringify(json));
        }else{
            wsc.send(JSON.stringify(json));
        }
        return;
    }

    // BDS登录完成 | 更新在线BDS信息
    if (purpose == "refOnlineBDSInfo"){
        ServerConfig = Res.body.data.serverConfig;
        OnlineBDSInfo = Res.body.data.onlineBDSInfo;
        logger.info("■ 当前在线BDS:");
        for (let key in OnlineBDSInfo){
            logger.info(`■ ${OnlineBDSInfo[key].ServerName}(${OnlineBDSInfo[key].uid}) | IP: ${OnlineBDSInfo[key].BDSIp} | 端口: ${OnlineBDSInfo[key].BDSPort}`);
        }
        let lastMainBDSOnline = mainBDS.online;
        mainBDS.online = false;
        // 更新主服务器信息
        for (let key in OnlineBDSInfo){
            if (OnlineBDSInfo[key].BDSType == 'main'){
                mainBDS.online = true;
                mainBDS.uid = OnlineBDSInfo[key].uid;
                mainBDS.BDSUid= OnlineBDSInfo[key].BDSUid;
                mainBDS.ServerName = OnlineBDSInfo[key].ServerName;
                mainBDS.BDSIp = OnlineBDSInfo[key].BDSIp;
                mainBDS.BDSPort = OnlineBDSInfo[key].BDSPort;
            }
        }

        // WS断线重连, 服务端连接设备信息更新 
        if (WSSstate.bdsReady == false){
            WSSstate.bdsReady = true;
            if (ServerConfig.SyncInventoryDB == '外置中心数据库'){
                if (mainBDS.online){
                    logger.error(`某BDS客户端配置文件(ClientConfig.json)错误。`);
                    logger.error(`WS服务端跨服同步玩家数据保存位置(SyncInventoryDB)选择 <外置中心数据库> 的情况下, 所有群组BDS的客户端配置(ServerType)都应设为"sub"`);
                }
                bdsReconnect();
            }
     
            if (ServerConfig.SyncInventoryDB == '主服存档'){
                if (mainBDS.online == true){
                    bdsReconnect();
                }
            }
            
        }

        // BDS持续连接(服务端独立运行)
        else if (WSSstate.bdsReady == true){
            if (ServerConfig.SyncInventoryDB == '主服存档'){
                // 检测主服重连
                if (mainBDS.online == true && lastMainBDSOnline == false){
                    bdsReconnect();
                }
                // 检测到主服断连（本副服WS连接未中断）
                if (mainBDS.online == false && lastMainBDSOnline == true){
                    if (SyncEnabled && SO.syncLLMoney){
                        for (let pl of mc.getOnlinePlayers()){
                            pl.disconnect(`§c主服务器已断开\n由于启用了LLMoney实时同步, 为防止失去同步, 本服禁止登入\n请联系服主启动主服务器`);
                            logger.warn(`<${pl.realName}> 主服务器已断开, 为防止LLMoney失去同步, 本服禁止登入`);
                        }
                    }
                }
            }
        }
        
    }

    // 处理订阅事件
    if (purpose == "subscribe"){
        Subscribe[Res.body.eventName] = true;
        return;
    }

    // 处理取消订阅事件
    if (purpose == "unsubscribe"){
        Subscribe[Res.body.eventName] = false;
        return;
    }

    //异步回调
    let eventid = Res.body?.data?.eventid;
    if (eventid != undefined){
        if (typeof wsSendCache.funcArr[eventid] == 'function'){
            wsSendCache.funcArr[eventid](Res.body?.data);//调用promise回调
            delete wsSendCache.funcArr[eventid];//调用完 摧毁掉 释放内存
            return;
        }
    }
}

// 监听命令输出s
function onConsoleCmd(){
    if (SyncEnabled == false){return;} //同步功能开关
    if (ServerType != 'main'){return;} //只有主服的命令才会同步到其他服
    mc.listen("onConsoleCmd",(command)=>{
        let cmdList = command.split(' ');
        if (config.get("SyncConfig")["SyncOption"].syncCmd.includes(cmdList[0])){
            wsSendEvent('BDServer', {
                tag: "broadcast",
                ignoreSelf: {
                    msg: false,
                    consoleMsg: false,
                    cmd: true
                },
                cmd: command,
                isShowCmd: true,
                requestBDSUid: BDSUid
            });
        }
    });
}

// 发送ws消息（与原版MC客户端Websocket通信使用）
function wsSend(json){
    let wsc = websocket.ws;
    if (wsc == undefined){return;}
    if (wsc.isLocal == true){ 
        //本地模拟客户端（是一个自定义类）
        wsc.ws.emit("message", json);
    }else{
        //真正的ws连接
        if (wsc.readyState == 1){
            wsc.send(json);
        }
    }
}

// 发送ws通用自定义事件数据 （自定义Websocket插件功能使用）
function wsSendEvent(eventName, _data){
    let wsc = websocket.ws;
    if (wsc == undefined){return;}
    let eventid = randStr();
    let promise = new Promise((resolve, reject) => {
        wsSendCache.funcArr[eventid] = resolve;//缓存resolve, 得到回复时再响应
        if (_data.eventid == undefined){
            _data.eventid = eventid; //加入事件id
        }
        wsSend(JSON.stringify({
            body: {
                userType: "server",
                user: ServerName, // 存档名
                data: _data,       //加入了eventid的data
            },
            header: {
                eventName: eventName,    //与MCws匹配，自定义事件名，区分正在进行的功能
                messagePurpose: 'event', //与MCws匹配
                version: 16908288,       //与MCws匹配
            }
        }));
    });
    setTimeout(()=>{
        if (eventid in wsSendCache.funcArr){
            wsSendCache.funcArr[eventid](undefined);
            delete wsSendCache.funcArr[eventid];
        }
    }, 10000);
    return promise;
}

// BDS重登录
function bdsReconnect(){
    mc.getOnlinePlayers().forEach((pl)=>{
        // 开始登录WS协调服务端：登记玩家
        if(pl.isSimulatedPlayer()){return;}//如果玩家是假人则直接跳过
        playerAllLogin(pl);
        logger.debug(`[BDS][${ServerName}][玩家重连中(1)] <${pl.realName}> 开始登录..`);
    });
    setTimeout(()=>{// 等待玩家重连完成
        // 检查修复意外退出状态
        checkPlayerLogout();
        // 更新本地缓存的协调服务端在线玩家数据
        wsSendEvent('BDServer', {
            tag: "onlinePlayers",
        });
    },3000);
}

//-------------------------------------------------------------
// LLSE监听

function onChat(){
    mc.listen("onChat",(pl, msg)=>{
        let wsc = websocket.ws;
        if (wsc == undefined){return;}
        if (wsc.isLocal != true){ 
            if(wsc.readyState != 1 && SyncEnabled == true){
                return;
            }
        }
        if (Subscribe["PlayerMessage"] == false){return;}
        if (msg.slice(0, 2) == "./" && ExecutorLevel == 1){
            if (!(pl.isOP())){
                return; // 执行者不是op则拦截./命令
            }
        }

        // 自动更改连接执行者
        if (msg.slice(0, 2) == "./"){
            Executor = pl;
        }

        let json = {
            body: {
                message: msg,
                receiver: pl.realName,
                sender: pl.realName,
                userType: "server",
                user: ServerName, // 存档名
                type: 'chat'
            },
            header: {
                eventName: 'PlayerMessage',
                messagePurpose: 'event',
                version: 16973824
            }
        };
        wsSend(JSON.stringify(json));
    });
}

function onJoin(){
    mc.listen("onPreJoin", (pl) => {
        playerPreLogin(pl);
    });
    mc.listen("onJoin", (pl) => {
        playerLogin(pl);// 开始登录WS协调服务端：登记玩家
    });
    //进服冻结玩家. 防刷物品
    mc.listen("onMobHurt", (mob,source,damage,cause)=>{
        if(mob.isPlayer()){
           if(mob.toPlayer().getExtraData("freeze")){return false;}
        }
    });
    mc.listen("onUseItem", (pl, it)=>{
        if (pl.getExtraData("freeze")){return false;}
    });
    mc.listen("onTakeItem", (pl, entity, it)=>{
        if (pl.getExtraData("freeze")){return false;}
    });
    mc.listen("onDropItem", (pl, it)=>{
        if (pl.getExtraData("freeze")){return false;}
    });
    mc.listen("onPlaceBlock", (pl, bl)=>{
        if (pl.getExtraData("freeze")){return false;}
    });
    mc.listen("onOpenContainerScreen", (pl, bl)=>{
        if (pl.getExtraData("freeze")){return false;}
    });
}

function onLeft(){
    mc.listen("onLeft", (pl) => {
        playerLogout(pl);
    });
}

function cmdRegister(){
    var cmd = mc.newCommand("sync", "WS协调服务端功能", PermType.Any); //PermType.Any: 任何人,  PermType.GameMasters: 仅OP, PermType.Console: 仅控制台
    cmd.setEnum("ListAction", ["list"]);
    cmd.setEnum("ListNumAction", ["num"]);
    cmd.setEnum("TransferAction", ["transfer"]);
    cmd.setEnum("PlayerDataAction", ["upload", "export", "import"]);

    cmd.setEnum("DirectAction", ["status", "backup", "stop", "reload"]);
    cmd.setEnum("ChangeAction", ["debug"]);

    cmd.mandatory("action", ParamType.Enum, "ListAction", 1);
    cmd.optional("listnum", ParamType.Enum, "ListNumAction", 1);
    cmd.mandatory("action", ParamType.Enum, "TransferAction", 1);
    cmd.mandatory("action", ParamType.Enum, "DirectAction", 1);
    cmd.mandatory("action", ParamType.Enum, "ChangeAction", 1);
    cmd.mandatory("action", ParamType.Enum, "PlayerDataAction", 1);

    cmd.mandatory("debugMode", ParamType.Bool);
    cmd.mandatory("BDSIp", ParamType.String);
    cmd.mandatory("BDSPort", ParamType.Int);
    cmd.mandatory("realName", ParamType.String);

    cmd.overload(["DirectAction"]);
    cmd.overload(["ListAction", "listnum"]);
    cmd.overload(["TransferAction"]);
    cmd.overload(["TransferAction", "BDSIp", "BDSPort"]);
    cmd.overload(["ChangeAction", "debugMode"]);
    cmd.overload(["PlayerDataAction", "realName"]);

    cmd.setCallback((_cmd, _ori, out, res) => {
        //logger.debug(`命令执行的主体: ${_ori.type}`); 玩家：0 控制台 7
        let pl = _ori.player;
        let response;
        let wsc;
        switch(res.action){
            // 列出群组服所有在线玩家
            case 'status':
                if (!(pl == null || pl.isOP() == true)){
                    out.error('此命令仅管理员可执行');
                    return;
                }
                wsc = websocket.ws;
                if (wsc == undefined){
                    out.success(`[!] 本服未连接到WS协调服务端`);
                    return;
                }
                if (wsc.isLocal != true){ 
                    if(wsc.readyState != 1){
                        out.success(`[!] 本服未连接到WS协调服务端`);
                        return;
                    }
                }

                response = '■ 当前在线BDS:';
                for (let key in OnlineBDSInfo){
                    if (OnlineBDSInfo[key].BDSType == 'main'){
                        response += `\n■【主】 ${OnlineBDSInfo[key].ServerName}(${OnlineBDSInfo[key].uid}) | IP: ${OnlineBDSInfo[key].BDSIp} | 端口: ${OnlineBDSInfo[key].BDSPort}`;
                    }else{
                        response += `\n■ ${OnlineBDSInfo[key].ServerName}(${OnlineBDSInfo[key].uid}) | IP: ${OnlineBDSInfo[key].BDSIp} | 端口: ${OnlineBDSInfo[key].BDSPort}`;
                    }
                    
                }
                out.success(response);
                break;

            case 'list':
                if (res.listnum == undefined){
                    out.success(getWSSPlayersList());
                    break;
                }else{
                    out.success(getWSSPlayersListNum());
                    break;
                }

            case 'reload':
                if (!(pl == null || pl.isOP() == true)){
                    out.error('此命令仅管理员可执行');
                    return;
                }
                configReload();
                out.success(`ClientConfig.json 重载完毕`);
                break;
            
            // 跨服传送
            case 'transfer':
                if (pl == null){
                    out.error('此命令仅允许玩家执行');
                    return;
                }
                if (res.BDSIp != null && res.BDSPort != null){
                    pl.transServer(res.BDSIp, res.BDSPort);
                    return;
                }
                transferMenu(pl);
                break;

            // 数据库热备份
            case 'backup':
                if (!(pl == null || pl.isOP() == true)){
                    out.error('此命令仅管理员可执行');
                    return;
                }

                response = syncbackup();
                if (response.success == true){
                    out.success(response.output);
                }else{
                    out.error(response.output);
                }
                break;

            // 安全退出服务器(先断开在线玩家)(请用此替代stop命令)
            case 'stop': 
                if (!(pl == null || pl.isOP() == true)){
                    out.error('此命令仅管理员可执行');
                    return;
                }
                syncstop();
                break;

            // debug模式
            case 'debug':
                if (!(pl == null || pl.isOP() == true)){
                    out.error('此命令仅管理员可执行');
                    return;
                }
                if (res.debugMode == true){
                    logger.setConsole(true, 5); 
                    out.success('WS协调服务端 Debug模式开启');
                }else{
                    logger.setConsole(true, 4); 
                    out.success('WS协调服务端 Debug模式关闭');
                }
                break;
            
            // 手动上传玩家数据
            case 'upload':
                if (pl != null && pl.isOP() == false){
                    out.error('此命令仅管理员可执行');
                    return;
                }
                if (_ori.type != 7){
                    out.error('为防止误操作, 此命令仅允许在控制台执行');
                }
                syncAllPlayersData(res.realName);
                out.success('开始执行..');
                break;

            case 'export':
                if (pl != null && pl.isOP() == false){
                    out.error('此命令仅管理员可执行');
                    return;
                }
                exportAllPlayersData(res.realName);
                out.success('开始执行..');
                break;

            case 'import':
                if (pl != null && pl.isOP() == false){
                    out.error('此命令仅管理员可执行');
                    return;
                }
                if (_ori.type != 7){
                    out.error('为防止误操作, 此命令仅允许在控制台执行');
                }
                importAllPlayersData(res.realName);
                out.success('开始执行..');
                break;
        }
    });
    cmd.setup();
}


//-------------------------------------------------------------
// 注册purpose服务

// 注册BDServer连接基础服务
function BDServer_serveice(bds_data){
    switch(bds_data.tag){
        // 云端反馈: 云端的在线玩家
        case "onlinePlayers":
            OnlinePlayers = bds_data.onlinePlayers;
            break;

        // 云端广播消息: 应用接受到的消息（游戏内和控制台）
        case "broadcast":
            if (bds_data.msg != "" &&  bds_data.msg != undefined){
                if (bds_data.ignoreSelf.msg == true){
                    if (bds_data.requestBDSUid != BDSUid){
                        mc.broadcast(bds_data.msg);
                    }
                }else{
                    mc.broadcast(bds_data.msg);
                }
            }
            if (bds_data.consoleMsg != "" && bds_data.consoleMsg != undefined){
                if (bds_data.ignoreSelf.consoleMsg == true){
                    if (bds_data.requestBDSUid != BDSUid){
                        logger.info(bds_data.consoleMsg);
                    }
                }else{
                    logger.info(bds_data.consoleMsg);
                }
            }
            if (bds_data.cmd != "" && bds_data.cmd != undefined){
                if (bds_data.ignoreSelf.cmd == true){
                    if (bds_data.requestBDSUid != BDSUid){
                        let res = mc.runcmdEx(bds_data.cmd);
                        if (bds_data.isShowCmd){
                            logger.info(`[同步命令] ${bds_data.cmd}`);
                            logger.info(`[同步命令] success: ${res.success}  | output: ${res.output}`);
                        }
                    }
                }else{
                    let res = mc.runcmdEx(bds_data.cmd);
                    if (bds_data.isShowCmd){
                        logger.info(`[同步命令] ${bds_data.cmd}`);
                        logger.info(`[同步命令] success: ${res.success}  | output: ${res.output}`);
                    }
                }
            }
            break;
    }
}

// 注册被动服务：同步相关
function sync_serveice(rcv_data){
    let res;
    switch(rcv_data.tag){
        // 云端请求(作为主服务器)：被请求指定玩家数据
        case 'getPlayerData':
            // 从本主服务器获取玩家数据并发送
            var playerInfo = pldb.get(rcv_data.playerXuid);
            var playerData;
            if (playerInfo != null){
                playerData =  getOfflinePlayerSyncData(rcv_data.playerXuid);
                wsSendEvent('Sync', {
                    tag: "playerData",
                    eventid: rcv_data.eventid,
                    success: true,
                    playerData: playerData,
                    playerUuid: rcv_data.playerUuid,
                    playerXuid: rcv_data.playerXuid,
                    playerRealName: rcv_data.playerRealName,
                    mainBDSName: ServerName,
                    mainBDSCnnUid: rcv_data.mainBDSCnnUid,
                    requestClientUid: rcv_data.requestClientUid,
                    requestClientName: rcv_data.requestClientName
                });
                logger.debug(`[Sync][${ServerName}(主)][玩家登录中(5)][数据请求] 已发送被请求的玩家数据: <${rcv_data.playerRealName}>`);   
            }else{
                //本主服务器没有该玩家的数据
                wsSendEvent('Sync', {
                    tag: "playerData",
                    eventid: rcv_data.eventid,
                    success: false,
                    reason: 'noPlayerInfo',
                    output: `失去同步 | 主服务器(${ServerName})没有 <${rcv_data.playerRealName}> 的基本信息, 请先登录主服务器`,
                    playerUuid: rcv_data.playerUuid,
                    playerXuid: rcv_data.playerXuid,
                    playerRealName: rcv_data.playerRealName,
                    mainBDSName: ServerName,
                    mainBDSCnnUid: rcv_data.mainBDSCnnUid,
                    requestClientUid: rcv_data.requestClientUid,
                    requestClientName: rcv_data.requestClientName
                });
                logger.debug(`[Sync][${ServerName}(主)][玩家登录中(5)][数据请求] 被请求的玩家基本信息不存在: <${rcv_data.playerRealName}>`);
            }
            break;
    
        // 云端数据(作为主服务器): 被请求更新指定玩家的数据
        case "syncPlayerData":
            if (ServerType == "main"){
                // 写入主数据库前处理： 按自定义同步配置对旧数据项替换新数据项
                
                let result = setPlayerData(rcv_data.playerData);
                if (result == true){
                    wsSendEvent('Sync', {
                        tag: "confirmSyncPlayerData",
                        eventid: rcv_data.eventid,
                        success: true,
                        mainBDSName: ServerName,
                        playerUuid: rcv_data.playerData.playerUuid,
                        playerXuid: rcv_data.playerData.playerXuid,
                        playerRealName: rcv_data.playerData.playerRealName,
                        mainBDSCnnUid: rcv_data.mainBDSCnnUid,
                        requestClientUid: rcv_data.requestClientUid,
                        requestClientName: rcv_data.requestClientName
                    });
                    
                    logger.debug(`[Sync][${ServerName}(主)][数据保存] <${rcv_data.playerData.playerRealName}> 的数据已更新成功`);
                }else{
                    wsSendEvent('Sync', {
                        tag: "confirmSyncPlayerData",
                        eventid: rcv_data.eventid,
                        success: false,
                        output: `[Sync][${ServerName}(主)][数据保存] <${rcv_data.playerData.playerRealName}> 的数据更新失败，写入数据库失败`,
                        playerUuid: rcv_data.playerData.playerUuid,
                        playerXuid: rcv_data.playerData.playerXuid,
                        playerRealName: rcv_data.playerData.playerRealName,
                        mainBDSName: ServerName,
                        mainBDSCnnUid: rcv_data.mainBDSCnnUid,
                        requestClientUid: rcv_data.requestClientUid,
                        requestClientName: rcv_data.requestClientName
                    });
                    
                    logger.warn(`[Sync][${ServerName}(主)][数据保存] <${rcv_data.playerData.playerRealName}> 的数据更新失败，写入数据库失败`);
                }
            }
            break;
        
        // 云端数据: 将收到的玩家数据导入本地存档
        case "syncDownloadPlayerData":
            res = setPlayerData(rcv_data.playerData, false, true);
            if (res == false){
                File.writeLine("./plugins/chatwss/unableDownLoadPlayers.txt", `${rcv_data.playerData.playerRealName} xuid: ${rcv_data.playerData.playerXuid} uuid: ${rcv_data.playerData.playerUuid}`);
            }
            break;
        
            // 云端数据(作为主服务器): 被请求实时更新指定玩家的LLmoney
        case "llmoney":
            OnLLmoney.add(rcv_data.playerXuid);
            money.set(rcv_data.playerXuid, rcv_data.value);
            logger.debug(`[Sync] 同步玩家(${rcv_data.playerXuid})LLmoney: ${rcv_data.value}`);
            OnLLmoney.delete(rcv_data.playerXuid);
            break;
    }
}



//-------------------------------------------------------------
// 玩家登录登出
async function playerPreLogin(pl){
    // 假人判断
    if(pl.isSimulatedPlayer()){return false;}

    pl.setExtraData("isPreJoining", true);

    // WS连接检查
    let wsc = websocket.ws;
    if (wsc == undefined){return false;}

    if (SyncEnabled == true){
        pl.setExtraData("freeze", true); //预登入时冻结玩家
        // WS连接检查
        if (wsc.isLocal != true){ 
            if(wsc.readyState != 1){
                logger.warn(`<${pl.realName}> 本服未连接到WS协调服务端, 为避免失去同步, 已阻止该玩家进入游戏`);
                pl.disconnect(`§c[${ServerName}] 本服未连接到WS协调服务端, 为避免失去同步, 本服已禁止玩家登入。\n§a如有问题请联系服主`);
                return false;
            }
        }

        // 主服连线检查
        if (!mainBDS.online && ServerConfig.SyncInventoryDB == '主服存档'){
            logger.warn(`<${pl.realName}> 主服务器不在线, 为避免失去同步，已阻止该玩家进入游戏`);
            pl.disconnect(`§c[${ServerName}] 主服务器不在线, 为避免失去同步, 本服已禁止玩家登入。\n§a如有问题请联系服主`);
            return false;
        }

        // BDS就绪检查（在收到refOnlineBDSInfo指令，并更新在线连接信息后，才允许玩家登录）
        if (WSSstate.bdsReady == false){
            logger.warn(`<${pl.realName}> 本服已连接WS协调服务端, 正在初始化BDS信息, 请稍后重新登录。`);
            pl.disconnect(`§c[${ServerName}] 本服已连接WS协调服务端, 正在初始化BDS信息, 请稍后重新登录。`);
            return false;
        }
        onJoinSync[pl.xuid] = {}; //预登陆状态，准备登录同步
    }

    let success = false;
    // 预登录检查（多设备检查）
    let bds_data = await wsSendEvent("BDServer", {
        tag: "playerPreLogin",
        loginTime: new Date().getTime(),
        playerXuid: pl.xuid,
        playerUuid: pl.uuid,
        playerRealName: pl.realName
    });

    if (bds_data == undefined){return false;}
    success = bds_data.success;

    // 登录协调服务端失败：多设备登录失败
    let pl_1 = mc.getPlayer(bds_data.playerXuid);
    if (!success){
        if (pl_1 == null){return false;}
        if (pl_1.getExtraData("playerPreLogin") != null){
            pl_1.delExtraData("playerPreLogin");
        }
        wsSendEvent('BDServer', {
            tag: "broadcast",
            ignoreSelf: {
                msg: false,
                consoleMsg: false,
                cmd: false
            },
            msg: `§e<${pl_1.realName}> §7登录§r ${ServerName} §7失败, 不允许多设备登录`,
            consoleMsg: `<${pl_1.realName}> 登录 ${ServerName} 失败, 不允许多设备登录`,
            requestBDSUid: BDSUid
        });
        logger.warn(`[BDS][${ServerName}][玩家登录中(3)] <${bds_data.playerRealName}> 不允许多设备登录同步服务器`);
        pl_1.disconnect(`§c<${pl_1.realName}> 登录 ${ServerName} 失败\n不允许多设备登录同步服务器`);
    }
    return success;
}

async function playerLogin(pl){
    // 假人检查
    if(pl.isSimulatedPlayer()){return false;}
    // WS连接检查
    let wsc = websocket.ws;
    if (wsc == undefined){return false;}
    let success = false;
    // 登录七秒后自动解除冻结状态
    setTimeout(()=>{
        if (pl.getExtraData("freeze") != null){
            pl.delExtraData("freeze");
        }
    },7000); 
    pl.setExtraData("isPreJoining", false);
    
    // 云端登记
    let bds_data = await wsSendEvent("BDServer", {
        tag: "playerLogin",
        loginTime: new Date().getTime(),
        playerXuid: pl.xuid,
        playerUuid: pl.uuid,
        playerRealName: pl.realName
    });
    if (bds_data == undefined){return false;}

    if (SyncEnabled == true){
        success = await SyncHandle(bds_data);
        if (success == undefined){return false;}
    }else{
        wsSendEvent("BDServer", {
            tag: "broadcast",
            ignoreSelf: {
                msg: true,
                consoleMsg: false,
                cmd: false
            },
            msg: `§e${bds_data.playerRealName} 加入了服务器 §7| ${ServerName}`,
            consoleMsg: `<${bds_data.playerRealName}> 加入了服务器(同步功能已关闭) | ${ServerName}`,
            requestBDSUid: BDSUid
        });
    }

    // 更新本地缓存的协调服务端在线玩家数据
    wsSendEvent('BDServer', {
        tag: "onlinePlayers",
    });
    return success; 
}

// 在线玩家重连
async function playerAllLogin(pl){
    let res = await playerPreLogin(pl);
    if (res == false){
        return false;
    }else{
        res = await playerLogin(pl);
    }
    return res;
}

async function playerLogout(pl){
    if(pl.isSimulatedPlayer()){return;}//如果玩家是假人则直接跳过
    if (pl.getExtraData("isPreJoining")){pl.delExtraData("isPreJoining");return;}
    if (SyncEnabled == true){pl.delExtraData("freeze");} //退出解除冻结状态

    // 保存玩家数据并同步到主服务器
    // 只有登录同步后才能退出同步, 登录时没有同步，那么退出时也不会进行同步
    if (SyncEnabled == true && onJoinSync[pl.xuid].success == true){
        let wsc = websocket.ws;
        if (wsc.isLocal != true){ 
            if(wsc.readyState != 1 && SyncEnabled == true){
                logger.warn(`<${pl.realName}> 本服未连接到WS协调服务端，该玩家置为意外退出状态`);
                return;
            }
        }
        syncPlayerData('onlinePlayer', pl, undefined, true); 
    }

    // 玩家在协调服务端注销
    let bds_data = await wsSendEvent('BDServer', {
        tag: "playerLogout",
        logoutTime: new Date().getTime(),
        playerXuid: pl.xuid,
        playerUuid: pl.uuid,
        playerRealName: pl.realName
    });
    if (bds_data == undefined){return false;}

    // [Sync] 玩家解除同步
    if (SyncEnabled == true){
        await SyncHandle(bds_data); //删除本地登录同步状态
    }

    if (bds_data.success == true){
        wsSendEvent('BDServer', {
            tag: "broadcast",
            ignoreSelf: {
                msg: true,
                consoleMsg: false,
                cmd: false
            },
            msg: `§e${bds_data.playerRealName} 退出了服务器 §7| ${ServerName}`,
            consoleMsg: `<${bds_data.playerRealName}> 退出了服务器 | ${ServerName}`,
            requestBDSUid: BDSUid
        });
    }else{
        logger.debug(`<${bds_data.playerRealName}> 登出WS协调服务端失败 | ${ServerName}`);
    }

    wsSendEvent('BDServer', {
        tag: "onlinePlayers",
    });
    return bds_data.success;
}

//-------------------------------------------------------------
// 同步
// [Sync] 检查玩家意外退出并执行同步修复 | 开服连接成功，玩家重登录后执行
function checkPlayerLogout(){
    // 玩家登录时, 是使用主数据库来判断意外退出
    // 本函数检查修复是依据本地数据库来判断。
    // 从主服意外退出: 主服为fasle，副服为true, 上次登录主服，主服开启发起修复: 只修改玩家意外退出状态, 其他所有副服的数据库仍为意外退出。 未修复时：玩家登录其他服务器时, 会强制登录主服, 修复后, 则正常从主服调取同步。
    // 从副服意外退出: 主服为fasle，副服为false, 上次登录副服，副服开启发起修复: 读取该玩家数据, 同步到主服务器, 修改本服数据
    // 副服的数据有效性：只有副服的上次登录状态是false时，即非正常退出状态，副服的其他数据才有效。
    if (SyncEnabled == false){return;} //同步功能开关
    if (!mainBDS.online && ServerConfig.SyncInventoryDB == '主服存档'){logger.error(`主服务器不在线, 无法完成意外退出自动修复。`);return;}
                
    let unFixPlayer = [];
    let unFixServerName = [];
    let isNeedFix = false;
    let keys;
    if (config.get("SyncConfig")["PlayerDbFormat"] == "leveldb"){
        keys = pldb.listKey();
    }else{
        keys = Object.keys(JSON.parse(pldb.read()));
    }
    for (let i = 0; i < keys.length; i++){
        let playerInfo = pldb.get(keys[i]);
        
        if (playerInfo.playerXuid == undefined){continue;} //略过GUID
        if (mc.getPlayer(playerInfo.playerXuid) != null){continue;} //只修复离线玩家的状态
        let ignore = false;
        for (let bdsUid in OnlinePlayers){
            if (OnlinePlayers[bdsUid].players.includes(playerInfo.playerRealName)){
                ignore = true;break;
            }
        }
        if (ignore == true){continue;}
        if (playerInfo.lastLogoutStatus == false){
            // 修复意外退出只能由意外退出的服务器发起
            if (playerInfo.BDSUid == BDSUid){
                //意外退出的服务器 向主服务器发起同步请求
                //获取该玩家离线数据
                let _needData = {
                    playerUuid: playerInfo.playerUuid,
                    playerXuid: playerInfo.playerXuid,
                    playerRealName: playerInfo.playerRealName,
                    playerSyncData: getOfflinePlayerSyncData(playerInfo.playerXuid).playerSyncData
                }; 
                //执行同步
                syncPlayerData('_needData', undefined, _needData, true);
                isNeedFix = true;
                colorLog("yellow", `<${playerInfo.playerRealName}> 上次在本服务器意外退出, 主服务器现已同步该玩家`);
            }else{
                if (!unFixPlayer.includes(playerInfo.playerRealName)){
                    unFixPlayer.push(playerInfo.playerRealName);
                }
                if (!unFixServerName.includes(playerInfo.ServerName)){
                    unFixServerName.push(playerInfo.ServerName);
                }
            }
        }
    }
    if (ServerType == 'main'){
        if (unFixPlayer.length == 0){
            logger.info(`[主服检查] 检查同步完毕, 现在没有玩家处于意外退出状态 | (此状态下可放心删除数据库)`);
        }else{
            colorLog("yellow", `[主服检查] ${unFixPlayer.length} 个玩家未能修复意外退出状态`);
            colorLog("yellow", `[主服检查] 当【意外退出的服务器】连接到【协调服务端】后将执行自动修复, 如该服务器没有自动修复，则需要该玩家重新登录`);
            colorLog("yellow", `[主服检查] 意外退出的服务器: ${unFixServerName.join(', ')}`);
            colorLog("yellow", `[主服检查] 意外退出的玩家: ${unFixPlayer.join(', ')}`);
        }  
    }else{
        if (isNeedFix){
            logger.info(`[副服检查] 从本服意外退出的玩家已检查修复完毕`);
        }else{
            logger.info(`[副服检查] 检查完毕, 没有从本服意外退出的玩家`);
        }
    }
}

// [Sync] 从主服务器请求同步文件
// [Sync操作] 登录登出同步流程(依赖wsSendEvent函数，以及其他同步处理函数)
async function SyncHandle(rcv_data){
    if (SyncEnabled != true){return true;} //没开同步则不执行
    let result;

    // 功能索引
    switch(rcv_data.tag){
        // 玩家登录
        case "playerLogin":
            result = await _playerLogin(rcv_data);
            if (result == undefined){return false;}
            break;

        // 云端登出
        case "playerLogout":
             // 玩家下线，解除本地登录同步记录。下次玩家上线需再次登录同步，才能开启自动同步。
            result = _playerLogout(rcv_data);
            break;

        // 云端反馈: 主服务器指定玩家数据的更新结果 
        case "confirmSyncPlayerData":
            if (rcv_data.success == false){
                logger.warn(rcv_data.output);
            }else{
                logger.debug(`[Sync][${ServerName}][数据保存] <${rcv_data.playerRealName}> 的数据已在主服务器更新成功`);
            }
            break;
    }

    // 登录同步 请求玩家数据
    async function _playerLogin(rcv_data){
        
        if (rcv_data.playerXuid == null || String(rcv_data.playerXuid).trim() == ''){return false;} //假人及未开启xuid验证则阻止同步

        // 刷新本地玩家基本信息
        let playerInfo = pldb.get(rcv_data.playerXuid);
        if (playerInfo == undefined){
            let pl_1 = mc.getPlayer(rcv_data.playerXuid);
            if (pl_1 == null){return false;}
            playerInfo = initPlayerInfo(pl_1);
            logger.debug(`[Sync][${ServerName}][玩家登录中(3)][数据请求] <${rcv_data.playerRealName}> ws协调服务端新人登录，初始化状态信息`);
        }
        
        // 向主服务器请求玩家数据
        logger.debug(`[Sync][${ServerName}][玩家登录中(3)][数据请求] <${rcv_data.playerRealName}> 云端已登记，请求玩家数据..`);
        if (ServerType == "main"){ //本服务器是主服务器，直接读取玩家数据
            //本服务器就是主服务器，无需经过ws请求，直接返回玩家数据
            playerData =  getOfflinePlayerSyncData(rcv_data.playerXuid);
            rcv_data = {
                tag: "playerData",
                success: true,
                playerData: playerData,
                playerXuid: rcv_data.playerXuid,
                playerUuid: rcv_data.playerUuid,
                playerRealName: rcv_data.playerRealName,
                mainBDSName: ServerName,
                mainBDSCnnUid: rcv_data.mainBDSCnnUid,
                requestClientUid: rcv_data.requestClientUid,
                requestClientName: rcv_data.requestClientName
            };
            logger.debug(`[Sync][${ServerName}(主)][玩家登录中(4)][数据请求] 登录本主服务器, 直接返回玩家数据: <${rcv_data.playerRealName}>`);

        }else{ // 本服务器是附属服务器，需要从主服务器调取玩家数据
            rcv_data = await wsSendEvent('Sync', {
                tag: "getPlayerData",
                playerData: getOfflinePlayerSyncData(rcv_data.playerXuid), // 携带本存档的数据，用于外置数据库初始化。
                playerXuid: rcv_data.playerXuid,
                playerUuid: rcv_data.playerUuid,
                playerRealName: rcv_data.playerRealName,
            }); 
            if (rcv_data == undefined){return false;}
        }
        
        // 应用收到的玩家数据 （在这里正式登录成功|失败）
        return login_rcv_playerData(rcv_data);
    }

    // 退出解除同步
    function _playerLogout(rcv_data){
        if (rcv_data.playerXuid == null || String(rcv_data.playerXuid).trim() == ''){return true;} //假人及未开启xuid验证则阻止同步
        delete onJoinSync[rcv_data.playerXuid];
        return true;
    }
    return result;
}

// [Sync] [同步主逻辑] 应用获取到的玩家数据 "playerData"
function login_rcv_playerData(_data){
    let pl_1 = mc.getPlayer(_data.playerXuid); // 登录的玩家
    if (pl_1 == null){return false;}
    /*
    - 获取玩家数据成功
        - 上次登录是本服务器
            - 登录成功 无需同步
        - 上次登录不是本服务器
            - 上次正常退出
                - 登录成功 按主服务器同步
            - 上次意外退出
                - 上次登录服务器在线
                    - 登录失败 传送至上次登录服务器
                - 上次登录服务器不在线
                    - 登录失败 断开连接
    - 获取玩家数据失败
    */
    // 登录成功
    if (_data.success == true){
        let setDataState; //判断读取玩家数据是否成功
        // 上次登录服务是本服务器
        if (_data.playerData.BDSUid == BDSUid){
            // 上次服务器是本服务器：无需读取数据库，直接登录成功
            _data.playerData.lastLogoutStatus = false;
            setPlayerData(_data.playerData, true);
            onJoinSync[pl_1.xuid] = { //登录状态信息，统一在结尾处理提示广播与断开连接
                success: true,
                msg: "",
                consoleMsg: "",
            };
            logger.debug(`[Sync][${ServerName}][玩家登录中(7)] <${_data.playerRealName}> 已登录到上次登录服务器(本服务器): 无需覆盖NBT，直接登录成功`);

        }else{
            // 上次服务器不是本服务器：检查上次退出情况
            // 上次正常退出：按主服务器同步
            if (_data.playerData.lastLogoutStatus == true){
                // 按主服务器同步
                logger.debug(`[Sync][${ServerName}][玩家登录中(7)] <${_data.playerRealName}> 上次正常退出, 继续登录: 按主服务器同步`);
                if (ServerType == "main"){
                    _data.playerData.lastLogoutStatus = false;
                    setPlayerData(_data.playerData, true);
                    onJoinSync[_data.playerXuid] = {
                        success: true,
                        msg: "",
                        consoleMsg: "",
                    };
                    logger.debug(`[Sync][${ServerName}][玩家登录中(7)] <${_data.playerRealName}> 上次正常退出，本次登录主服务器, 无需覆盖NBT，直接登录成功`);
                }else{
                    _data.playerData.lastLogoutStatus = false;
                    setDataState = setPlayerData(_data.playerData);
                    if (setDataState == true){
                        onJoinSync[_data.playerXuid] = {
                            success: true,
                            msg: "",
                            consoleMsg: "",
                        };
                    }else{
                        onJoinSync[_data.playerXuid] = {
                            success: false,
                            msg: `§c同步数据失败: 写入收到的玩家数据时出错 | ${ServerName}`,
                            consoleMsg: `[Sync][${ServerName}][玩家登录中(7)] <${_data.playerRealName}> 写入收到的玩家数据时出错 | 断开连接`,
                        };
                    }
                }
                
                
            }

            // 上次意外退出：传送至上次服务器（防止物品丢失）
            else{
                // 上次服务器——在线 则传送
                let lastBDSInfo = queryBDSInfo(_data.playerData.BDSUid);
                if (lastBDSInfo.success){
                    onJoinSync[pl_1.xuid] = {
                        success: false,
                        msg: `§e传送玩家至上次登录服务器: ${_data.playerData.ServerName} | ${ServerName}`,
                        consoleMsg: `[Sync][${ServerName}][玩家登录中(7)] <${_data.playerRealName}> 上次意外退出, 正在传送至上次登录服务器: ${_data.playerData.ServerName} | 断开连接`,
                    };

                    logger.warn(onJoinSync[pl_1.xuid].consoleMsg);
                    
                    pl_1.delExtraData("freeze");
                    if(pl_1.transServer(lastBDSInfo.BDSIp, lastBDSInfo.BDSPort) == false){
                        pl_1.sendText(`§c传送至 ${_data.playerData.ServerName} 服务器失败`);
                        logger.warn(`<${pl_1.realName}> 传送至 ${_data.playerData.ServerName} 服务器失败`);
                    }
                    return onJoinSync[_data.playerXuid].success;
                }else{
                    // 上次服务器——不在线 
                    if (SafeLeftMode){
                        onJoinSync[pl_1.xuid] = {
                            success: false,
                            msg: `§c登录同步失败: 上次从服务器(${_data.playerData.ServerName})意外退出, 而该服务器现在没有开启 | ${ServerName}`,
                            consoleMsg: `[Sync][${ServerName}][玩家登录中(7)] <${_data.playerRealName}> 上次从服务器(${_data.playerData.ServerName})意外退出, 而该服务器现在没有开启。 如服主有需要，可关闭配置文件中的SafeLeftMode | 断开连接`,
                        };
                    }else{
                        // 关闭安全模式情况下 则按主服务器同步（可能会丢失物品）
                        // 读取来自主服务器的玩家数据
                        logger.debug(`[Sync][${ServerName}][玩家登录中(7)] <${_data.playerRealName}> 上次登录服务器不在线: ${_data.playerData.ServerName}, 继续登录: 按主服务器同步`);
                        setDataState = setPlayerData(_data.playerData);
                        if (setDataState == true){
                            onJoinSync[_data.playerXuid] = {
                                success: true,
                                msg: "",
                                consoleMsg: "",
                            };
                        }else{
                            onJoinSync[_data.playerXuid] = {
                                success: false,
                                msg: `§c同步数据失败: 读取收到的玩家数据时出错 | ${ServerName}`,
                                consoleMsg: `[Sync][${ServerName}][玩家登录中(7)] <${_data.playerRealName}> 读取收到的玩家数据时出错 | 断开连接`,
                            };

                        }
                    }
                    
                }

            }
        }
        
        // 上次登录检查: 登录成功: 
        if (onJoinSync[pl_1.xuid].success == true){
            wsSendEvent('BDServer', {
                tag: "broadcast",
                ignoreSelf: {
                    msg: true,
                    consoleMsg: false,
                    cmd: false
                },
                msg: `§e${_data.playerRealName} 加入了服务器 §7| ${ServerName}`,
                consoleMsg: `<${_data.playerRealName}> 加入了服务器 | ${ServerName}`,
                requestBDSUid: BDSUid
            });
            syncPlayerData('onlinePlayer', pl_1); //立即同步一次
        }
        // 上次登录检查: 登录失败：
        else{
            logger.warn(onJoinSync[pl_1.xuid].consoleMsg);
            wsSendEvent('BDServer', {
                tag: "broadcast",
                ignoreSelf: {
                    msg: false,
                    consoleMsg: false,
                    cmd: false
                },
                msg: `§e<${pl_1.realName}> §7登录 ${ServerName} 同步失败, 已断开连接`,
                consoleMsg: `<${pl_1.realName}> 登录 ${ServerName} 同步失败, 已断开连接`,
                requestBDSUid: BDSUid
            });
            pl_1.disconnect(`§c<${pl_1.realName}> 登录 ${ServerName} 时 失去同步\n${onJoinSync[pl_1.xuid].msg}`);
        }

        pl_1.delExtraData("freeze");
        return onJoinSync[pl_1.xuid].success;
    }

    //登录失败: 请求玩家数据失败
    else{
        pl_1.delExtraData("freeze");
        if (_data.reason == 'noPlayerInfo'){
            if (!AllowCreateNewPlayer){
                onJoinSync[_data.playerXuid] = {
                    success: false,
                    msg: '§c'+_data.output,
                    consoleMsg: `${_data.output}`,
                };
                logger.warn(onJoinSync[pl_1.xuid].consoleMsg);
                wsSendEvent('BDServer', {
                    tag: "broadcast",
                    ignoreSelf: {
                        msg: false,
                        consoleMsg: false,
                        cmd: false
                    },
                    msg: `§e<${pl_1.realName}> §7登录 ${ServerName} 同步失败, 已断开连接`,
                    consoleMsg: `<${pl_1.realName}> 登录 ${ServerName} 同步失败, 已断开连接`,
                    requestBDSUid: BDSUid
                });

                if (mainBDS.online && ServerConfig.SyncInventoryDB == '主服存档'){
                    mc.broadcast(`<${pl_1.realName}> §e正在自动传送至主服务器: §r${mainBDS.ServerName}`);
                    logger.warn(`<${pl_1.realName}> 正在自动传送至主服务器: ${mainBDS.ServerName}`);
                    if(pl_1.transServer(mainBDS.BDSIp, mainBDS.BDSPort) == false){
                        pl_1.sendText(`§c传送至 ${mainBDS.ServerName} 服务器失败`);
                        logger.warn(`<${pl_1.realName}> 传送至 ${mainBDS.ServerName} 服务器失败`);
                    }else{
                        //pl_1.disconnect(`§c<${pl_1.realName}> 登录 ${ServerName} 时 失去同步\n${onJoinSync[pl_1.xuid].msg}`);
                    }
                }else{
                    logger.warn(`<${pl_1.realName}> 无法自动传送, 主服务器不在线`);
                    pl_1.disconnect(`§c<${pl_1.realName}> 登录 ${ServerName} 时 失去同步\n${onJoinSync[pl_1.xuid].msg}`);
                }

            }else{
                onJoinSync[pl_1.xuid] = {
                    success: true,
                    msg: "",
                    consoleMsg: "",
                };
                wsSendEvent('BDServer', {
                    tag: "broadcast",
                    ignoreSelf: {
                        msg: true,
                        consoleMsg: false,
                        cmd: false
                    },
                    msg: `§e[新玩家] ${_data.playerRealName} 加入了服务器 §7| ${ServerName}`,
                    consoleMsg: `[新玩家] <${_data.playerRealName}> 加入了服务器 | ${ServerName}`,
                    requestBDSUid: BDSUid
                });
                syncPlayerData('onlinePlayer', pl_1); //立即同步一次
                return true;
            }
        }

        if(_data.reason == 'noMainBDS'){
            onJoinSync[_data.playerXuid] = {
                success: false,
                msg: '§c'+_data.output,
                consoleMsg: `${_data.output}`,
            };
            logger.warn(onJoinSync[pl_1.xuid].consoleMsg);
            wsSendEvent('BDServer', {
                tag: "broadcast",
                ignoreSelf: {
                    msg: false,
                    consoleMsg: false,
                    cmd: false
                },
                msg: `§e<${pl_1.realName}> §7登录 ${ServerName} 同步失败, 已断开连接`,
                consoleMsg: `<${pl_1.realName}> 登录 ${ServerName} 同步失败, 已断开连接`,
                requestBDSUid: BDSUid
            });

            pl_1.disconnect(`§c<${pl_1.realName}> 登录 ${ServerName} 时 失去同步\n${onJoinSync[pl_1.xuid].msg}`);
        }
        return onJoinSync[_data.playerXuid].success;
    }
}

// [Sync] [玩家在线&离线] 同步该在线玩家到主服务器 | 核心同步函数
async function syncPlayerData(mode, pl, _needData, lastLogoutStatus = false){
    if (SyncEnabled == false){return false;} //同步功能开关
    //两种模式，指定pl则是同步在线玩家，指定needData是同步离线玩家（同步给定的数据），用于副服的意外退出修复

    // 获取该在线玩家同步所需资料
    if (mode == 'onlinePlayer'){
        if (pl == null){return false;} //玩家对象获取失败则不执行
        if (pl.getExtraData("isPreJoining")){return false;} //没有进服则不执行

        let score = {};
        for (let scoreName of GMLIB_SC.getAllObjectives()){
            score[scoreName] = {displayName: GMLIB_SC.getDisplayName(scoreName), value: GMLIB_SC.getPlayerScore(pl.uuid, scoreName)};
        }
        
        _needData = {
            playerUuid: pl.uuid,
            playerXuid: pl.xuid,
            playerRealName: pl.realName,
            playerSyncData: {
                playerNBT: data.toBase64(pl.getNbt().toBinaryNBT()),
                LLMoney: money.get(pl.xuid),
                scoreboard: score,
            }
        };
    }
    
    //更新本地玩家状态
    let newPlayerInfo = {
            VER: INFO.version,
            time: new Date().getTime(),
            playerUuid: _needData.playerUuid,
            playerXuid: _needData.playerXuid,
            playerRealName: _needData.playerRealName,
            ServerName: ServerName,
            BDSUid: BDSUid,
            BDSIp: BDSIp,
            BDSPort: BDSPort,
            lastLogoutStatus: lastLogoutStatus
        };
    pldb.set(_needData.playerXuid, newPlayerInfo);
    if (ServerType == 'main'){
        // 主服务器 同步数据
        logger.debug(`[Sync][${ServerName}(主)][数据保存] <${_needData.playerRealName}> 已更新本地玩家状态信息(同步至此完毕)`);
        return true;
    }
 
    // 附属服务器 同步数据
    if (ServerType == 'sub'){
        // playinfo中加入NBT信息
        logger.debug(`[Sync][${ServerName}][数据保存] <${_needData.playerRealName}> 本地玩家状态已更新, 开始请求主服务器保存玩家数据..`);
        let rcv_data = await wsSendEvent('Sync', {
            tag: "syncPlayerData", 
            playerData: {
                VER: INFO.version,
                time: new Date().getTime(),
                playerSyncData: _needData.playerSyncData, // 同步内容
                playerUuid: _needData.playerUuid,
                playerXuid: _needData.playerXuid,
                playerRealName: _needData.playerRealName,
                ServerName: ServerName,
                BDSUid: BDSUid,
                BDSIp: BDSIp,
                BDSPort: BDSPort,
                lastLogoutStatus: lastLogoutStatus
            },
            //syncConfig: SO  // 同步配置选项，决定对主数据库哪一项覆盖，哪一项忽略
        });
        if (rcv_data == undefined){return false;}

        if (rcv_data.success == false){
            logger.warn(rcv_data.output);
        }else{
            logger.debug(`[Sync][${ServerName}][数据保存] <${rcv_data.playerRealName}> 的数据已在主服务器更新成功`);
        }
        
        return rcv_data.success;
    }else{
        logger.error(`ServerType error, must be 'sub' or 'main'`);
        return false;
    }
}

// [Sync] [同步主逻辑] LLmoney实时同步
function onLLMoney(){
    if (!SyncEnabled){return;}
    //双向实时同步llmoney
    mc.listen("onMoneyAdd", (xuid, num)=>{
        if(!SO.syncLLMoney){return;}
        if (OnLLmoney.has(xuid)){return;} 
        wsSendEvent('Sync', {
            tag: "llmoney",
            playerXuid: xuid,
            value: money.get(xuid),
        }); 
    });

    mc.listen("onMoneyReduce", (xuid, num)=>{
        if(!SO.syncLLMoney){return;}
        if (OnLLmoney.has(xuid)){return;} 
        wsSendEvent('Sync', {
            tag: "llmoney",
            playerXuid: xuid,
            value: money.get(xuid),
        });
    });

    mc.listen("onMoneySet", (xuid, num)=>{
        if(!SO.syncLLMoney){return;}
        if (OnLLmoney.has(xuid)){return;} 
        wsSendEvent('Sync', {
            tag: "llmoney",
            playerXuid: xuid,
            value: money.get(xuid),
        });
    });
}

// [Sync] [服主指令] 将本服所有玩家同步外置中心数据库。
async function syncAllPlayersData(realName){
    // WS连接检查
    let wsc = websocket.ws;
    if (wsc.isLocal != true){ 
        if(wsc.readyState != 1){
            logger.error(`本服未连接到WS协调服务端, 无法使用此功能`);
            return false;
        }
    }

    // 主服连线检查
    if (!mainBDS.online && ServerConfig.SyncInventoryDB == '主服存档'){
        logger.error(`主服务器不在线, 无法使用此功能`);
        return false;
    }

    // if (realName == 'all' && ServerConfig.SyncInventoryDB == '主服存档'){
    //     logger.error("此功能仅当玩家数据存储在 外置中心数据库 时有效");
    //     return false;
    // }
    let allXuids;
    if (realName != 'all'){
        let xuid = data.name2xuid(realName);
        let uuid = data.name2uuid(realName);
        if (xuid == null || xuid == "" || uuid == null || uuid == ""){
            logger.error(`没有查找到有关玩家 ${realName} 的记录`);
            return false;
        }
        allXuids = [{name: realName, xuid: xuid, uuid: uuid}];
    }else{
        allXuids = data.getAllPlayerInfo();
    }
    if (File.exists(`./plugins/chatwss/unableUploadPlayers.txt`)){
        File.delete(`./plugins/chatwss/unableUploadPlayers.txt`);
    }
    

    logger.warn(`正在上传 ${allXuids.length} 名玩家的同步数据, 请稍等...`);
    let unUploadNum = 0;
    let startTime = new Date().getTime();
    for (let obj of allXuids){
        let score = {};
        for (let scoreName of GMLIB_SC.getAllObjectives()){
            score[scoreName] = {displayName: GMLIB_SC.getDisplayName(scoreName), value: GMLIB_SC.getPlayerScore(obj.uuid, scoreName)};
        }

        if (GMLIB_MC.getPlayerNbt(obj.uuid) == null){
            if (unUploadNum == 0){File.writeLine("./plugins/chatwss/unableUploadPlayers.txt", `realName, xuid, uuid`);}
            unUploadNum++;
            File.writeLine("./plugins/chatwss/unableUploadPlayers.txt", `${obj.name}, ${obj.xuid}, ${obj.uuid}`);
            continue;
        }

        let playerSyncData = {
            playerNBT: data.toBase64(GMLIB_MC.getPlayerNbt(obj.uuid).toBinaryNBT()),
            LLMoney: money.get(obj.xuid),
            scoreboard: score,
        };

        let _needData = {
            playerUuid: obj.uuid,
            playerXuid: obj.xuid,
            playerRealName: obj.name,
            playerSyncData: playerSyncData
        }; 

        //执行同步
        let res = await syncPlayerData('_needData', undefined, _needData, true);
        if (res == false){
            if (unUploadNum == 0){File.writeLine("./plugins/chatwss/unableUploadPlayers.txt", `realName, xuid, uuid`);}
            unUploadNum++;
            logger.warn(`上传玩家数据时发生错误: ${obj.name} xuid: ${obj.xuid} uuid: ${obj.uuid}`);
        }
    }
    let TotalTime = new Date().getTime() - startTime;
    logger.warn(`已上传 ${allXuids.length - unUploadNum} 名玩家的同步数据，耗时: ${(TotalTime/1000).toFixed(2)}s`);
    if (unUploadNum != 0){
        logger.warn(`无法上传 ${unUploadNum} 名玩家的同步数据, 存档没有这些玩家的数据或上传过程中发生错误。详见: ./plugins/chatwss/unableUploadPlayers.txt`);
    }
    return true;
}

// [Sync] [服主指令] 导出为外置中心数据库。
function exportAllPlayersData(realName){
    let allXuids;
    if (realName != 'all'){
        let xuid = data.name2xuid(realName);
        let uuid = data.name2uuid(realName);
        if (xuid == null || xuid == "" || uuid == null || uuid == ""){
            logger.error(`没有查找到有关玩家 ${realName} 的记录`);
            return false;
        }
        allXuids = [{name: realName, xuid: xuid, uuid: uuid}];
    }else{
        allXuids = data.getAllPlayerInfo();
    }

    if (File.exists(`./plugins/chatwss/export/exportPLAYERDATA`)){
        File.delete(`./plugins/chatwss/export/exportPLAYERDATA`);
    }
    if (File.exists(`./plugins/chatwss/unableExportPlayers.txt`)){
        File.delete(`./plugins/chatwss/unableExportPlayers.txt`);
    }
    let CENTERDATA_db = new KVDatabase(`./plugins/chatwss/export/exportPLAYERDATA`); // 玩家数据库路径;
    
    logger.warn(`正在导出 ${allXuids.length} 名玩家的同步数据, 请稍等...`);
    let startTime = new Date().getTime();
    let unableExportNum = 0;
    for (let obj of allXuids){
        let score = {};
        for (let scoreName of GMLIB_SC.getAllObjectives()){
            score[scoreName] = {displayName: GMLIB_SC.getDisplayName(scoreName), value: GMLIB_SC.getPlayerScore(obj.uuid, scoreName)};
        }

        if (GMLIB_MC.getPlayerNbt(obj.uuid) == null){
            if (unableExportNum == 0){File.writeLine("./plugins/chatwss/unableExportPlayers.txt", `realName, xuid, uuid`);}
            unableExportNum++;
            File.writeLine("./plugins/chatwss/unableExportPlayers.txt", `${obj.name}, ${obj.xuid}, ${obj.uuid}`);
            continue;
        }
        let playerSyncData = {
            playerNBT: data.toBase64(GMLIB_MC.getPlayerNbt(obj.uuid).toBinaryNBT()),
            LLMoney: money.get(obj.xuid),
            scoreboard: score,
        };

        let _needData = {
            playerUuid: obj.uuid,
            playerXuid: obj.xuid,
            playerRealName: obj.name,
            playerSyncData: playerSyncData
        }; 

        //执行同步
        CENTERDATA_db.set(obj.uuid, _needData);
    }
    let TotalTime = new Date().getTime() - startTime;
    logger.warn(`已导出 ${allXuids.length-unableExportNum} 名玩家的同步数据，耗时: ${(TotalTime/1000).toFixed(2)}s`);
    if (unableExportNum != 0){
        logger.warn(`[无法导出] 无法导出 ${unableExportNum} 名玩家的同步数据，因为中途存档更换导致存档内没有该玩家的数据。`);
        logger.warn(`[无法导出] 请注意将./plugins/LiteLoader/PlayerDB.db与你的存档一起备份或更换, 这个数据库存有玩家xuid库, 注意在升级BDS版本时也不要覆盖它。`);
        logger.warn(`[无法导出] 无法导出的玩家名单见: ./plugins/chatwss/unableExportPlayers.txt`);
    }
    logger.warn(`已导出的玩家数据见 ./plugins/chatwss/export/exportPLAYERDATA。可作为WS服务端外置中心数据库。`);
    CENTERDATA_db.close();
    return true;
}

// [Sync] [服主指令] 导入外置中心数据库中的数据。
function importAllPlayersData(realName){
    if (!File.exists(`./plugins/chatwss/export/exportPLAYERDATA`)){
        logger.error(`中心数据库不存在, 请将exportPLAYERDATA文件夹放到./plugins/chatwss/export/下。`);
        return false;
    }
    if (File.exists(`./plugins/chatwss/unableImportPlayers.txt`)){
        File.delete(`./plugins/chatwss/unableImportPlayers.txt`);
    }
    let CENTERDATA_db = new KVDatabase(`./plugins/chatwss/export/exportPLAYERDATA`); // 玩家数据库路径;

    let listKey;
    if (realName != 'all'){
        let xuid = data.name2xuid(realName);
        let uuid = data.name2uuid(realName);
        if (xuid == null || xuid == "" || uuid == null || uuid == ""){
            logger.error(`没有查找到有关玩家 ${realName} 的记录, 无法为没进服的玩家导入数据`);
            CENTERDATA_db.close();
            return false;
        }
        listKey = [uuid];
    }else{
        listKey = CENTERDATA_db.listKey();
    }

    let startTime = new Date().getTime();
    let missNum = 0;
    logger.warn(`正在导入 ${listKey.length} 名玩家的同步数据...`);
    for (let uuid of listKey){
        let playerData = CENTERDATA_db.get(uuid);
        res = setPlayerData(playerData, false, true);
        if (res == false){
            if (missNum == 0){File.writeLine("./plugins/chatwss/unableImportPlayers.txt", `realName, xuid, uuid`);}
            missNum++;
            File.writeLine("./plugins/chatwss/unableImportPlayers.txt", `${playerData.playerRealName}, ${playerData.playerXuid}, ${playerData.playerUuid}`);
        }
    }
    let TotalTime = new Date().getTime() - startTime;
    logger.warn(`已导入 ${listKey.length-missNum} 名玩家的同步数据，耗时: ${(TotalTime/1000).toFixed(2)}s`);
    if (missNum!=0){
        logger.warn(`无法导入 ${missNum} 名玩家的同步数据, 因为这些玩家未进过本服, 详见: ./plugins/chatwss/unableImportPlayers.txt`);
    }
    CENTERDATA_db.close();    
}

//-------------------------------------------------------------
// 同步函数
// [Sync] 同步一次当前在线玩家
function SyncOnlinePlayerData(){
    if (SyncEnabled == false){return;} //同步功能开关
    let wsc = websocket.ws;

    // 作为LLSE插件
    if (IsLLSEPlugin == true){
        // ws连接正常
        if (wsc == undefined){return;}
        if (wsc.readyState == 1){
            let players = mc.getOnlinePlayers();
            if (players.length != 0){
                logger.debug("///////// 玩家数据自动同步 /////////////");
            }
            for (let i = 0; i<players.length; i++){
                if(players[i].isSimulatedPlayer()){continue;}//如果玩家是假人则直接跳过
                //只有登录同步成功的玩家才能开启自动同步。 避免首次登录过程中向主服务器自动同步而造成的数据丢失。
                if (onJoinSync[players[i].xuid] == undefined){ // 防一手undefined错误
                    onJoinSync[players[i].xuid] = {};
                }
                if (onJoinSync[players[i].xuid].success == true){ 
                    syncPlayerData('onlinePlayer', players[i]);
                }
            }
        }

    // 作为Lib
    }else{
        let players = mc.getOnlinePlayers();
        if (players.length != 0){
            logger.debug("///////// 玩家数据自动同步 /////////////");
        }
        for (let i = 0; i<players.length; i++){
            if(players[i].isSimulatedPlayer()){continue;}//如果玩家是假人则直接跳过
            //只有登录同步成功的玩家才能开启自动同步。 避免首次登录过程中向主服务器自动同步而造成的数据丢失。
            if (onJoinSync[players[i].xuid] == undefined){ // 防一手undefined错误
                onJoinSync[players[i].xuid] = {};
            }
            if (onJoinSync[players[i].xuid].success == true){ 
                syncPlayerData('onlinePlayer', players[i]);
            }
        }
    }
}

// [Sync] 按同步配置，将收到的玩家数据写入本存档 | 最后应用玩家数据
// 用于登录同步、每30s自动同步、退出同步
function setPlayerData(data_, onlyPlayerInfo = false, onlyPlayerData = false) {
    if (SyncEnabled == false){return false;} //同步功能开关
    if (data_ == null) {logger.error('玩家数据为空，写入主存档失败');return false;}
    //if (ServerType != 'main'){logger.error('非主服务器存档无法写入玩家数据');return false;}
    let playerInfo = pldb.get(data_.playerXuid); // 获取该玩家的本地uuid
    if (onlyPlayerData == false && !AllowCreateNewPlayer && playerInfo == null){
        logger.error('主服务器没有记录该玩家的基本信息');
        return false;
    }
    let syncConfig = SO; //接收同步配置

    // [写入数据库] 重写离线玩家的基本信息
    // playinfo中去除NBT信息
    let newPlayerInfo = {
        VER: INFO.version,
        time: new Date().getTime(),
        playerUuid: data_.playerUuid,
        playerXuid: data_.playerXuid,
        playerRealName: data_.playerRealName,
        ServerName: data_.ServerName,
        BDSUid: data_.BDSUid,
        BDSIp: data_.BDSIp,
        BDSPort: data_.BDSPort,
        lastLogoutStatus: data_.lastLogoutStatus
    };

    
    if (onlyPlayerData == false){
        pldb.set(data_.playerXuid, newPlayerInfo);
    }
    

    // [仅更新玩家状态信息] 登录上次登录服务器时，无需覆盖NBT数据
    if (onlyPlayerInfo == true){
        return true;
    }

    // 检查玩家是否存在 因为LL2无法对不存在的玩家写入存档 (LL3 可以写入)
    // if (onlyPlayerData == true){
    //     if (!GMLIB_MC.getAllPlayerUuids().includes(data_.playerUuid)){
    //         return false;
    //     }
    // }

    // [写入存档NBT]
    //File.writeTo('./playerNBT.js', data_.playerSyncData.playerNBT);
    //logger.debug(`[Sync][${ServerName}][存档写入] <${data_.playerRealName}> ${playerInfo.playerUuid} 写入tag配置: ${getSOArray(syncConfig)}`);
    if (getSOArray(syncConfig).length==0){return true;}
    if (GMLIB_MC.getPlayerNbt(data_.playerUuid) == null){ // 创建新玩家 GMLIB_MC.getPlayerNbt(data_.playerUuid) == null
        let nbt = NBT.parseBinaryNBT(data.fromBase64(data_.playerSyncData.playerNBT, true));
        if (nbt == null){
            //File.writeTo("./plugins/chatwss/illegalNbt.txt", data_.playerSyncData.playerNBT);
            logger.error(`[Sync][${ServerName}][存档写入] <${data_.playerRealName}> NBT解析错误, xuid: ${data_.playerXuid}, uuid: ${data_.playerUuid}`);
            return false;
        }
        let setSuccess = GMLIB_MC.setPlayerNbt(data_.playerUuid, nbt);
        if (!setSuccess){
            logger.error(`[Sync][${ServerName}][存档写入] 新玩家 <${data_.playerRealName}> NBT写入失败`);
            return false;
        }
        let wordSpawnPos = GMLIB_MC.getWorldSpawn();
        GMLIB_MC.setPlayerPosition(data_.playerUuid, wordSpawnPos);
        GMLIB_MC.setPlayerSpawnPoint(data_.playerUuid, wordSpawnPos);
    }else{  // 正常覆盖nbt
        let nbt = NBT.parseBinaryNBT(data.fromBase64(data_.playerSyncData.playerNBT, true));
        if (nbt == null){
            //File.writeTo("./plugins/chatwss/illegalNbt.txt", data_.playerSyncData.playerNBT);
            logger.error(`[Sync][${ServerName}][存档写入] <${data_.playerRealName}> NBT解析错误, xuid: ${data_.playerXuid}, uuid: ${data_.playerUuid}`);
            return false;
        }
        let setSuccess = GMLIB_MC.setPlayerNbtTags(data_.playerUuid, nbt, getSOArray(syncConfig));
        if (!setSuccess){
            logger.error(`[Sync][${ServerName}][存档写入] <${data_.playerRealName}> NBT写入失败`);
            return false;
        }
    }
    logger.debug(`[Sync][${ServerName}][存档写入] <${data_.playerRealName}> NBT写入成功: ${data_.playerUuid} `);
    //logger.debug(`[Sync][${ServerName}][存档写入] <${data_.playerRealName}> NBTtag配置: ${getSOArray(syncConfig)}`);

    //[写入计分板]
    if (syncConfig.syncScoreboard.length != 0){
        let score = data_.playerSyncData.scoreboard;
        for (let scoreName in score){
            //按配置同步相应计分项
            if (!syncConfig.syncScoreboard.includes(scoreName)){continue;} 
            let allScoreName = GMLIB_SC.getAllObjectives();

            //计分板不存在则新建该计分板
            if (!allScoreName.includes(scoreName)){
                GMLIB_SC.addObjective(scoreName, score[scoreName].displayName);
            }

            //该玩家没有分数则重置该玩家分数
            if (score[scoreName].value == null){
                GMLIB_SC.resetPlayerScore(data_.playerUuid, scoreName);
                continue;
            }

            // 同步该玩家分数
            if (GMLIB_SC.setPlayerScore(data_.playerUuid, scoreName, score[scoreName].value)){
                logger.debug(`[Sync][${ServerName}][存档写入] <${data_.playerRealName}> scoreboard 写入成功`);
            }else{
                logger.error(`[Sync][${ServerName}][存档写入] scoreboard写入失败 ${data_.playerRealName} ${scoreName} ${score[scoreName].value}`);
                logger.error(score);
            }
        }
    }
    
    // [写入LLMoney]
    if(syncConfig.syncLLMoney) {
        setTimeout(() => {
            try {
                money.set(data_.playerXuid, data_.playerSyncData.LLMoney);
                logger.debug(`[Sync][${ServerName}][存档写入] <${data_.playerRealName}> LLMoney写入成功`);
            }
            catch(e) {
                logger.error(`[Sync][${ServerName}][存档写入] <${data_.playerRealName}> LLMoney写入失败 ` + e);
            }
        }, 300);//防止覆盖
    }
    
    return true; 
}

// [Sync] [玩家在线] [写入数据库] 刷新本地玩家基本信息 | 协调服务端登录成功时调用
function initPlayerInfo(pl){
    if (SyncEnabled == false){return;} //同步功能开关
    if (pl == null){return;} //玩家对象获取失败则不执行
    if (pl.getExtraData("isPreJoining")){return;} //没有进服则不执行
    //if (pldb.get(pl.xuid) != null){return true;} // 已初始化则无动作
    // 记录玩家基本临时信息记录 | 本地db数据库存这些，每个人的数据上线都会全部刷新。
    let Data = { //本地存储8项数据
        VER: INFO.version,
        time: new Date().getTime(),
        playerUuid: pl.uuid,
        playerXuid: pl.xuid,
        playerRealName: pl.realName,
        ServerName: ServerName,
        BDSUid: BDSUid,
        BDSIp: BDSIp,
        BDSPort: BDSPort,
        lastLogoutStatus: false
    };
    pldb.set(pl.xuid, Data);
    logger.debug(`[sync] 玩家基本信息刷新: 本存档uuid ${pl.uuid}`);
    return Data;
}

// [Sync] [玩家离线] 调取同步需求的三项数据, 返回携带playerSyncData的playerInfo, 即playerData | 登录同步过程中,主服务器被请求时调用
function getOfflinePlayerSyncData(xuid){
    let playerInfo = pldb.get(xuid);
    if (playerInfo == null ){
        logger.error(`获取离线数据失败, 本地没有该玩家的记录 ${xuid}`);
        return null;
    }

    let score = {};
    for (let scoreName of GMLIB_SC.getAllObjectives()){
        score[scoreName] = {displayName: GMLIB_SC.getDisplayName(scoreName), value: GMLIB_SC.getPlayerScore(playerInfo.playerUuid, scoreName)};
    }

    playerInfo['playerSyncData'] = {
        playerNBT: data.toBase64(GMLIB_MC.getPlayerNbt(playerInfo.playerUuid).toBinaryNBT()),
        LLMoney: money.get(xuid),
        scoreboard: score,
    };

    let playerData = playerInfo;
    return playerData;
}

// [Sync] 定时自动同步
function AutoSyncPlayerData(){
    setInterval(() => {
        SyncOnlinePlayerData();
    }, AutoSyncCycle);
}

// [Sync] 数据库定时热备份
function ScheduleBackup(){
    if (config.get("SyncConfig").BackupEnabled == false){
        return;
    }
    try{
        if (typeof(BackupTime) == "string"){
            new CronJob(BackupTime, function () { 
                syncbackup();
            }, null, true,'Asia/Chongqing');
        }else{
            for (let t of BackupTime){
                new CronJob(t, function () { 
                    syncbackup();
                }, null, true,'Asia/Chongqing');
            }
        }
    }catch(e){
        logger.error('启动自动备份失败, 检查ClientConfig.json BackupTime Cron表达式配置, 不能有问号');
    }
}

// [Sync] 数据库热备份
function syncbackup(){
    if (SyncEnabled == false){return;} //同步功能开关

    // 删除15天前的备份
    let filesList = File.getFilesList(BackupDir);
    let current = new Date();
    if (filesList.length > BackupRemainDays){
        let canDeleteNum = filesList.length - BackupRemainDays;
        for (let i = 0; i< filesList.length; i++){
            let res = /.*_(\d*)-(\d*)-(\d*)_[\d]*_[\d]*_[\d]*/.exec(filesList[i]);
            let difference = current - new Date(Number(res[1]), Number(res[2])-1, Number(res[3]));
            if (Math.floor(difference / 86400000) > BackupRemainDays){
                File.delete(`${BackupDir}/${filesList[i]}`);
                logger.info(`已删除 ${BackupRemainDays} 天前的备份: ${filesList[i]}`);
            }
            canDeleteNum --;
            if (canDeleteNum <= 0){
                break;
            }
        }
    }
    

    let DbFileDir = `${BackupDir}/${Levelname}_${system.getTimeStr()}`.replace(/\s/g, "_").replace(/:/g, "_");
    logger.info(`玩家数据库开始备份: ${DbFileDir}`);
    let backupDb;
    let keys;
    if (config.get("SyncConfig")["PlayerDbFormat"] == "leveldb"){
        backupDb = new KVDatabase(DbFileDir);
        keys = pldb.listKey();
    }else{
        backupDb = new JsonConfigFile(DbFileDir+"/pldb.json","{}"); // 玩家数据库路径;
        keys = Object.keys(JSON.parse(pldb.read()));
    }

    logger.debug(`原始数据键列表: ${keys}`);
    for (let i = 0; i < keys.length; i++){
        if (backupDb.set(keys[i], pldb.get(keys[i])) == false){
            logger.error(`数据库备份失败: ${keys[i]} 写入错误`);
            backupDb.close();
            File.delete(DbFileDir);
            return {success:false, output:`数据库备份失败: ${keys[i]} 写入错误`}; 
        }
    }
    //logger.debug(`备份数据键列表: ${backupDb.listKey()}`);
    if (backupDb.close() == true){
        logger.info(`玩家数据库备份成功: ${DbFileDir}`);
        return {success:true, output:`玩家数据库备份成功: ${DbFileDir}`};
    }else{
        logger.error(`玩家数据库关闭错误: ${DbFileDir}`);
        return {success:false, output:`玩家数据库关闭错误: ${DbFileDir}`}; 
    }      
}

// 安全退出服务器(先断开在线玩家)(替代stop命令)
function syncstop(){
    logger.warn("######## 协调服务端安全退出进程 ########");
    mc.broadcast(`[${ServerName}] §c十五秒后关服维护`);
    let count = 15;
    let taskID = setInterval(()=>{
        mc.broadcast(`§c---- §e${count} 秒后关服维护 §c----`);
        logger.warn(`---- ${count} 秒后踢出玩家 ----`);
        count--;
        if (count <= 0){
            mc.getOnlinePlayers().forEach((pl)=>{
                pl.disconnect(`[${ServerName}] 服主关服维护`);
            });
            logger.info("######## 五秒后关闭服务器 ########");
            setTimeout(()=>{
                mc.runcmdEx('stop');
            },5000);
            clearInterval(taskID);
        }
    },1000);
}


function transferMenu(pl){
    let fm_transSrv = mc.newSimpleForm();
    fm_transSrv.setTitle("§c§l传送至同步服务器...");
    fm_transSrv.setContent(`\n§b选择一个服务器:\n`);
    let BDSs = [];
    for (let key in OnlineBDSInfo){
        if (OnlineBDSInfo[key].BDSUid == BDSUid){
        continue; //不传送到本服务器
        }
        fm_transSrv.addButton(`§l${OnlineBDSInfo[key].ServerName}`);
        BDSs.push(OnlineBDSInfo[key]);
    }
    
    pl.sendForm(fm_transSrv, transSrv_run);

    function transSrv_run(pl, id){
        if (id == null){return;}
        pl.sendModalForm('§c§l确认传送', `\n\n§b现在传送至 §r${BDSs[id].ServerName} ?\n\n`, "§2§l✈ 立即传送", "§7§l取消", (pl, res)=>{
            if (res == true){
                mc.broadcast(`§e${pl.realName} 正在传送至 §r${BDSs[id].ServerName}`);
                pl.transServer(BDSs[id].BDSIp, BDSs[id].BDSPort);
            }else{
                pl.sendForm(fm_transSrv, transSrv_run);
            }
        });
    }
}

//-------------------------------------------------------------
// 其他函数

function getWSSPlayersListNum(){
    let plCount = 0;
    for (let key in OnlinePlayers){
        plCount += OnlinePlayers[key].players.length;
    }
    return String(plCount);
}
function getWSSPlayersList(){
    let plCount = 0;
    let listRes = '';
    for (let key in OnlinePlayers){
        listRes += `\n\n[${OnlinePlayers[key].ServerName}] \n${OnlinePlayers[key].players.join(', ')}`;
        plCount += OnlinePlayers[key].players.length;
    }
    return `当前在线(${plCount}):` + listRes.slice(1);
}

//随机字符串
function randStr() {
    wsSendCache.count++;
    let arr_str = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];
    return String(`${Date.now()}${randNum(100, 999)}`).match(/./g).map(v => (Number(v) % 2 == 0) ? arr_str[Number(v)].toUpperCase() : arr_str[Number(v)]).join('') + wsSendCache.count;
}

function randNum(min = 0, max = 255) {
    return Math.floor(Math.random() * max) + min;
}
// 获取本机公网IP
function getPublicIP() {
  const os = require("os");
  const ifaces = os.networkInterfaces();
  let en0;

  Object.keys(ifaces).forEach((ifname) => {
    let alias = 0;

    ifaces[ifname].forEach(function (iface) {
      if ("IPv4" !== iface.family || iface.internal !== false) {
        // skip over internal (i.e. 127.0.0.1) and non-ipv4 addresses
        return;
      }

      if (alias >= 1) {
        // this single interface has multiple ipv4 addresses
        en0 = iface.address;
        //console.log(ifname + ":" + alias, iface.address);
      } else {
        // this interface has only one ipv4 adress
        //console.log(ifname, iface.address);
        en0 = iface.address;
      }
      ++alias;
    });
  });
  return en0;
}

// 检查对应IP端口的BDS是否云端在线
function queryBDSInfo(_BDSUid){
    let res = {success:false, BDSIp:"127.0.0.1",BDSPort:19132};
    for (let key in OnlineBDSInfo){
        if (OnlineBDSInfo[key].BDSUid == _BDSUid){
            res.success = true;
            res.BDSIp = OnlineBDSInfo[key].BDSIp;
            res.BDSPort = OnlineBDSInfo[key].BDSPort;
        }
    }
    return res;
}

// 开始运行

// 作为本地lib客户端 由main3d.js调用（通信不经过WS连接, websocket不是真正的WS连接而是一个类）
if (File.exists("./plugins/chatwss/main3d.js")){
    // 此时，本BDS服务器运行Nodejs插件，开设Websocket服务端，此js文件会作为本BDS服务器的Websocket模拟客户端，不用另外安装客户端插件。
    // 导出处理ws服务端命令json的函数，以及监听本地聊天的函数
    // 仅消息发送的代码稍微不一样 wsc.send 换成 wsc.ws.emit
    IsLLSEPlugin = false;

    // 这种情况下，开设Websocket服务端的BDS自动作为同步服务器中的主服务器。
    // 若想将其他服务器作为主服务器，则需将这两行注释掉，并将主服务器客户端插件配置中的ServerType修改为'main'。
    
    // let SyncConfig = config.get("SyncConfig");
    // if (SyncConfig.ServerType != 'main'){
    //     SyncConfig.ServerType = 'main';
    //     config.set("SyncConfig", SyncConfig);
    //     ServerType = 'main';
    //     logger.warn(`检测到本BDS运行了WS协调服务端(chatwss_Server), 已自动修改配置, 本BDS将作为主服务器（中心服务器）`);
    // }

    let JSevents = require('events');
    class LocalSimulationWs {
        constructor(){
            this.isLocal = true; 
            this.ws = new JSevents.EventEmitter();
        }
        on(event, callback){
            this.ws.on(event, callback); // ws服务端处理
        }
        send(message){
            onTextReceived(message, {ws:this}); // 发送给客户端 | 监听收到的数据
        }

    }
    websocket.ws = new LocalSimulationWs();
    logger.info(`WS协调客户端(本地)(ver.${INFO.version}) 已连接到 ${ConnectUrl}`);
    logger.info(`游戏内输入 ./help 查看帮助`);

    try{
        PAPI = require("../../GMLIB-LegacyRemoteCallApi/lib/BEPlaceholderAPI-JS").PAPI;
        //require('../../../lib/BEPlaceholderAPI-JS').PAPI;
        PAPI.registerServerPlaceholder(getWSSPlayersListNum, 'chatwss', 'wss_onlinePlayersNum');
        PAPI.registerServerPlaceholder(getWSSPlayersList, 'chatwss', 'wss_onlinePlayers');
    }catch(e){
        logger.warn('未导出PAPI变量 在线玩家 wss_onlinePlayers 在线人数 wss_onlinePlayersNum ');
    }

    try{
        GMLIB_MC = require("../../GMLIB-LegacyRemoteCallApi/lib/GMLIB_API-JS").Minecraft;
        GMLIB_SC = require("../../GMLIB-LegacyRemoteCallApi/lib/GMLIB_API-JS").Scoreboard;
    }catch(e){
        logger.error(e);
        logger.error('需要前置插件: GMLIB-LegacyRemoteCallApi');
    }

    ScheduleBackup(); // 开启定时备份功能
    onChat(); //监听聊天
    onJoin(); //监听加入
    onLeft(); //监听退出
    onConsoleCmd(); //监听命令输出
    onLLMoney(); // LLMoney 实时同步
    cmdRegister(); //监听启动：命令注册
    AutoSyncPlayerData(); // 开启自动同步
    

    // 导出需要Websocket的函数
    // module.exports = {
    //     onTextReceived,
    //     onChat,
    //     onJoin,
    //     onLeft,
    //     AutoSyncPlayerData
    // };
}

// 则作为单独客户端插件独立运行（通信经过WS连接, websocket为真正的WS连接）
else{
    llseWsClient(); //插件注册，开始连接，自动同步，心跳检测 onTextReceived()  AutoSyncPlayerData() checkPlayerLogout()
    onChat(); //监听聊天
    onJoin(); //监听加入
    onLeft(); //监听退出
    onConsoleCmd(); //监听命令输出
    onLLMoney(); // LLMoney 实时同步
}



// 导出发送同步事件的api
function wsBroadcast(msg, consoleMsg = "", cmd = ""){
    wsSendEvent('BDServer', {
        tag: "broadcast",
        ignoreSelf: {
            msg: false,
            consoleMsg: false,
            cmd: false
        },
        msg: msg,
        consoleMsg: consoleMsg,
        cmd: cmd,
        isShowCmd: false,
        requestBDSUid: BDSUid
    });
}

ll.export(wsBroadcast, 'chatwss', 'wsBroadcast');
ll.export(wsSendEvent, 'chatwss', 'wsSendEvent');



/*
# testfor @s 的客户端答复
{
  body: { statusCode: 0, statusMessage: '发现 Wn1027', victim: [ 'Wn1027' ] },
  header: {
    messagePurpose: 'commandResponse',
    requestId: 'initName@1',
    version: 16908288
  }
}


# 服务端命令
"body": {
    "commandLine": cmd,
    "version": 1
},
header: {
    requestId: tag+"@"+this.uid,
    messagePurpose: "commandRequest",
    version: 1,
    messageType: 'commandRequest'
}

# 普通命令的客户端答复
{
  body: { statusCode: 0 },
  header: {
    messagePurpose: 'commandResponse',
    requestId: 'broadcast@',
    version: 16908288
  }
}

# tellraw命令的客户端答复
{
  body: {
    message: '{"rawtext":[{"text":"§e[18:55:08]§b<Wn1027> §e当前在线: §r1个连接"}]}\n',
    receiver: 'Wn1027',
    sender: '外部',
    type: 'tell'
  },
  header: {
    eventName: 'PlayerMessage',
    messagePurpose: 'event',
    version: 16908288
  }
}
#title 命令
{
  body: {
    message: 'progress: 0.09%',
    receiver: 'Wn1027',
    sender: 'Wn1027',
    type: 'title'
  },
  header: {
    eventName: 'PlayerMessage',
    messagePurpose: 'event',
    version: 16973824
  }
}

# setblock命令的客户端答复
{
  body: {
    position: { x: 0, y: -60, z: 0 },
    statusCode: 0,
    statusMessage: '方块已放置'
  },
  header: {
    messagePurpose: 'commandResponse',
    requestId: 'broadcast@',
    version: 16908288
  }
}

*/